一点一滴探究 JVM 以内存结构

前言

我一直尝试着用不同的文字来写博客!缘由很简单,你讲的知识书上都有,那么每一个人为何不选择看书而选择看你的博文来学习呢?由于书上的内容都是大片大片描述性的文字,对于jvm这块的知识,又是异常枯燥,但又不能不学习的硬骨头!这刚好也就能说明Head First系列的书籍为何比较火的缘由,每一个人接收图形知识的速度每每比文字性的东西要快不少。从此我也会尝试用本身的特点来写博客,尽可能能引发读者的兴趣,能从中学到东西,我就满足了!java

今天的一点一滴探究JVM系列,打算复习一下jvm内存结构!至于学习这块知识的好处?一,从面试的角度来看,你了解jvm,而且java基础扎实,你才更有竞争力(由于我本人本科还没毕业,因此考虑问题常常从面试者的角度来考虑)。其二,提升你对java的理解,知道你建立的每个对象,每个变量,都在什么地方,若是不知道这些稀里糊涂得写代码,总会有一天会”翻车”的!好了,废话很少说了,咱们开始正题吧!c++

开始以前

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的”墙”, 墙外的人想进去,墙内的人想出来。 或许你常常看到StackOverFlowError, OutOfMemoryError无从下手,由于你压根不知道,到底是什么东西形成内存爆了,固然,你也没法解决!面试

举个简单的例子bash

public class test {
    private int f() {
        f();
    }
    public static void main(String[] args) {
        f();
    }
}
复制代码

这个简单的递归,不对,它不算是递归,由于没有终止条件,可是你知道它最终会报什么错误,知道为何会报这个错误吗?到底是那块内存发生了错误?多线程

这个问题,咱们留在后面回答,是留在后面你本身解答,看完这篇博文,不用我说,这些问题你都会很清楚!相信我!架构

目标

你可能会好奇,你看完这篇文章你能学到什么?jvm

清楚你的对象会被分配在哪里(不绝对) 理解哪些区域对线程来讲是私有区,哪些区域是线程共享区域 知道方法调用发生了什么? … 等等等,你可能还会解释你之前遇到一些匪夷所思的问题!总之,你若是以前没了解过这些知识,那么这些东西对你来讲,就是成长!学习

墙内的世界

你可能很好奇,墙内到底是什么样?接下来跟着我一探究竟ui

上图就是jvm比较详细的内存划分,下面咱们来按线程私有共享来划分jvm内存区spa

下面咱们来着重介绍一下这几块内存区域

程序计数器(Program Counter Register)

什么是程序计数器呢,学过汇编的都知道,cs:ip组成的物理地址是下一条要执行的指令的地址,来吧!看图

咱们能够很清楚的看到,当前cs:ip指向的内存地址刚好就是咱们要执行的下一条指令的位置,前面咱们图中(按线程私有共享划分jvm内存的图)又说了,程序计数器是线程私有的,再联想一下我举cs:ip的例子,咱们能够很天然的想到,程序计数器其实就是记录线程当前执行到了哪一条指令,由于什么要记录这个值呢?由于,若是咱们有不少个线程,线程执行顺序又是不可预料的,假如某一时刻咱们在执行线程A里面的指令,而后线程B又得到了cpu的资源,去执行去线程B的指令,假如再过了一段时间以后,A又得到了cpu的资源,想吃回头草,此时回到线程A执行,它不知道要执行线程A的哪条指令!这是没有程序计数器所造成的尴尬局面,可是有了线程私有的程序计数器,这个问题就不存在了,这就是程序计数器出现的缘由,以及它的用处,我想你看完这段文字,应该已经对程序计数器这个概念彻底理解了!

另外,我须要说明的一点是,程序计数器是Java虚拟机规范中惟一一个没有规定任何内存错误的区域!

虚拟机栈(Vm Stack)

这块区域是干啥的?为啥也是线程私有的?

虚拟机栈描述的是Java方法执行的内存模型 咱们来解读这句话,为何说Vm Stack是描述Java方法执行的内存模型呢?其实:

每一个方法执行的时候都会建立一个栈帧(Stack Frame)的东西,学过c/c++的应该都对这个概念熟悉。栈帧用于存储局部变量表、操做数栈、动态连接、方法出口信息等。每一个方法从调用开始到结束的过程,都对应这Vm Stack中的入栈出栈的过程!这也就能回答开头咱们看到的那个问题了,很简单错误在单线程状况下确定是StackOverFlowError,多线程下OutOfMemoryError(上图已经写得很清楚了)

好比

public void test() {
    String name = "stormma";
    int age = 21;
}
JAVA架构群:678779467
复制代码

上面的例子的age变量和name引用都是存储在虚拟机栈的栈帧里面的(由于咱们前面说过了,一个方法从开始调用到结束调用的过程都对应着一个Vm Stack出栈入栈的过程)。

咱们前面说了,这块区域存储了局部变量表,操做数栈,动态连接,还有方法出口信息等,我想你应该比较好奇这几个概念。

局部变量表: 局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,其中存放的数据的类型是编译期可知的各类基本数据类型、对象引用(reference)和(returnAddress)类型(它指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成计算的,即在Java程序被编译成Class文件时,就肯定了所需分配的最大局部变量表的容量。当进入一个方法时,这个方法须要在栈中分配多大的局部变量空间是彻底肯定的,在方法运行期间不会改变局部变量表的大小。

操做数栈: 操做数栈又常被称为操做栈,操做数栈的最大深度也是在编译的时候就肯定了。32位数据类型所占的栈容量为1, 64位数据类型所占的栈容量为2。当一个方法开始执行时,它的操做栈是空的,在方法的执行过程当中,会有各类字节码指令(好比:加操做、赋值元算等)向操做栈中写入和提取内容,也就是入栈和出栈操做。Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操做数栈。所以咱们也称Java虚拟机是基于栈的,这点不一样于Android虚拟机,Android虚拟机是基于寄存器的。基于栈的指令集最主要的优势是可移植性强,主要的缺点是执行速度相对会慢些;而因为寄存器由硬件直接提供,因此基于寄存器指令集最主要的优势是执行速度快,主要的缺点是可移植性差

动态连接: 每一个栈帧都包含一个指向运行时常量池(在方法区中,后面介绍)中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程当中的动态链接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如 final、static 域等),称为静态解析,另外一部分将在每一次的运行期间转化为直接引用,这部分称为动态链接。

方法返回地址: 当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,而且该异常没有在方法体内获得处理。不管采用何种退出方式,在方法退出以后,都须要返回到方法被调用的位置,程序才能继续执行。方法返回时可能须要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。通常来讲,方法正常退出时,调用者的PC计数器的值就能够做为返回地址,栈帧中极可能保存了这个计数器值,而方法异常退出时,返回地址是要经过异常处理器来肯定的,栈帧中通常不会保存这部分信息。方法退出的过程实际上等同于把当前栈帧出站,所以退出时可能执行的操做有:恢复上层方法的局部变量表和操做数栈,若是有返回值,则把它压入调用者栈帧的操做数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令。 我想关于这个区域的东西我已经介绍完了,我想你也应该懂了。

下面咱们来下一个区域: 堆(heap)

堆(Heap)

堆区,是一块颇有意思的区域,为啥有意思,由于这块区域是全部线程共享的,也是咱们大部分的对象的聚居地(为啥说是大部分呢?这个概念咱们以后的文章会进行详细的讲解,若是你特别好奇,能够看一下我以前的文章, Java逃逸分析)!也是jvm管理的最大一块内存(对了,上面的图的大小不表明内存占比,只是为了看着舒服而已)!也是gc开展工做的主要区域。

堆内存中分为一块区域,用于存储类信息,静态变量等等数据,这一块区域以前叫作方法区后面又叫永久带,以后更名叫作Meta-Area/Meta Space Area,元数据空间,名字不重要,咱们要清楚这块区域是什么做用就好了!

Meta-Area

这块区域也是线程共享的区域,它主要存储jvm加载类的类信息,类变量,常量(这个在meta-area的常量区),即时编译器编译后的代码等数据。

运行时常量区

这个区域是Meta-Area的一部分,用于存放编译器生成的各类字面量和符号引用,这部份内容将在类加载后存放到方法区的运行时常量池中。这在咱们的上一篇博客有所涉及。

枯燥概念性的东西看完以后,咱们来看一个例子,来加深一下这块的印象:

public void test() {
    Object obj = new Object();
}
JAVA架构群:678779467
复制代码

对于这段代码会涉及Vm Stack、Java Heap、Meta-Area三个最重要的内存区域。

结合咱们前面的例子,由于test()方法涉及到Vm Stack区,我想你应该明白,obj会存放在局部变量表中,new Object(),咱们前面说过咱们大部分的对象都会存储在Java Heap这个区域,因此,Java Heap存储了这个实例对象!那么你会很好奇,Meta-Area为啥会涉及到呢?

咱们知道Meta-Area存储了类的信息,类变量常量等等东西!由于咱们实例化Object对应的时候,要用到Object这个类的信息,因此它会访问Meta-Area的Object.class这个Class对象来得到一些实例化对象须要的东西。

对了,做为补充,我想你还须要知道, obj引用怎么你能访问到Java Heap区的那个实例化对象

有两种方式,一种使用过句柄指针(学过c/c++对这些概念应该会很熟悉)

还有一种就是经过指针直接访问

上图来自深刻理解JVM一书

本地方法栈(Native Method Stack)

这块区域相对来讲,没有前面几个概念重要。

该区域与虚拟机栈所发挥的做用很是类似,只是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为使用到的本地操做系统(Native)方法服务。

好比Java调用c/c++/汇编就用到这块区域

结尾

我想你看完这篇博文,应该达到了咱们文章开始以前的目标!这篇文章介绍的比较浅显,本着用例子来解释说明内存区域的做用,这样我想你会更容易接收,总比大片的文字描述让你更有兴趣!若是你有什么建议或者疑惑,能够经过GitHub联系我!

相关文章
相关标签/搜索