JVM内存模型

JVM由程序计数器,虚拟机栈,本地方法栈,堆,方法区这五大区域组成。具体分配如图所示。


共享区域:方法区,堆

线程隔离:虚拟机栈,本地方法栈,程序计数器

1. 程序计数器

        每个线程独有,各个线程之间计数器互不影响,独立存储,指向下一条指令的地址。

2. 虚拟机栈

        java虚拟机栈描述的是java方法执行的内存模型,每个方法在执行的时候都会创建一个栈帧。

        用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从调用到执行完成的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

        1)局部变量表:存放编译期间可知的各种基本数据类,,对象引用或对象本身,编译时确定

        2)操作数栈:所有操作码都是对操作栈上的数进行操作,对每一个方法的调用,jvm都会建立一个操作数栈,以供计算使用,栈深度在将源码编译为class文件,就确定了。

        3)动态链接:每个栈帧内部都包含一个指向运行时常量池的引用来支持当前方法的代码实现动态链接

        4)方法出口:正常返回,使调用者的代码能被调用的方法返回并且返回值被推入调用者栈帧的操作数栈后继续正常执行;抛异常,则不会有返回值被返回。

3. 本地方法栈

        与虚拟机栈类似,为native方法服务

4. 堆

        所有new出来的东西都在堆上分配(对象实例和数组)。堆被分成新生代(YongGeneration)和老年代(OldGeneration),新生代又分成Eden和两个Survivor区。

        1)MinorGC

        新生代是所有新对象产生的地方。当新生代内存空间被用完时,就会触发垃圾回收。这个垃圾回收叫做MinorGC。新生代被分为3个部分——Enden区和两个Survivor区。

        新对象在新生代产生,当新生代空间占满后,会触发MinorGC开始垃圾回收,大多数新建对象位于Eden区,一些大对象(例如:大数组)则会直接进入老年代,Eden区满后触发一次MinorGC,存活下来的对象转到survivor0区,survivor0区满后触发执行MinorGC,survivor0区存活对象全部转入survivor1区,这样保证一段时间内总有一个survivor区为空。经过多次MinorGC仍然存活的对象会转入老年代,这是由设定的年龄阈值来决定的。

        2)MajorGC

        老年代空间存储长期存活的对象,老年代占满时会触发MajorGC,花费时间较长,由于垃圾回收会导致“stop the world”事件,即所有线程都会停下来等待垃圾回收执行完成,所以对响应要求高的应用应尽量减少发生MajorGC,如微博后台程序发生MajorGC就会导致前台页面刷新超时,若是股票等实时交易应用发生MajorGC导致响应超时,可能会给客户带来损失,这都是系统调优要考虑到的问题。

        3)FullGC

        清理整个堆空间,包括新生代和老年代。

        触发场景:年代晋升失败,比如eden区的存活对象晋升到Survivor区放不下,又尝试直接晋升到Old区又放不下,这时会发生FullGC。

        在分代收集算法中,由于新生代中对象的存活率低,通常使用复制算法进行垃圾回收。老年代一般使用"标记-清理"或"标记-整理"算法回收存活率较高的对象。

5. 方法区

        被各个线程共享的内存区域,用于寻出已被虚拟机加载的类信息,常量,静态变量,即时编译的代码(类变量,类方法),运行时常量池等。

        在1.6时,方法区一般被称为永久代,其实本质上两者并不等价,是用永久代管理的方法区。但容易遇到内存溢出问题;在1.7时,已经将原本放在方法区中的字符串常量池,字面量,类的静态变量转移到堆中,将符号引用转移到了native heap中;在1.8时,移除了整个永久代,取而代之的是一个叫元空间的区域。元空间在直接内存中,不在虚拟机栈中,因此元空间的大小依赖于内存大小,当然你也可以自定义元空间大小。

为什么叫元空间呢?因为这里边存放的是类的元数据信息。

        元数据,是用来描述数据的数据。简单的说,就是最小的数据单元。元数据可以为数据说明其元素或属性(名称、大小、数据类型等)或其结构(长度、字段、数据列)或其相关数据(位于何处、如何联系、拥有者)。

        写到这时,发现自己不太清楚运行时常量池与class常量池是什么关系,就顺便查下资料,分享下我查到的资料。

字符串常量池,class常量池,运行时常量池

        字符串常量池:存放字符串常量,

        class常量池:我们写的每一个Java类被编译后,就会形成一份class文件,class文件中除了包含类的版本,字段,方法,接口等描述信息外,还有一项信息就是常量池,用于存放编译器生成的各种字面量和符号引用.

        字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;

        符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。

         运行时常量池: 运行时常量池更具动态性,在运行期间也可以将新的变量放入常量池中,而不是一定要在编译时确定的常量才能放入,类加载后,class常量池也存放于这里.

你可能感兴趣的:(JVM内存模型)