Java虚拟机在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域。每个区域都有各自的作用。分析JVM内存结构,就是分析JVM运行时数据存储区域。
JVM的运行时数据区主要包括:堆、虚拟机栈、方法区、程序计数器、本地方法栈。
其中,堆和方法区是所有线程共享的,栈、本地方法栈和程序计数器则为线程私有的。
1.堆(又称GC堆)
堆是java虚拟机管理内存最大的一块内存区域,堆内存被所有线程共享,主要存放new关键字创建的对象,所有对象实例及数组都要在堆上分配空间。
垃圾收集器就是根据GC算法,收集堆上所占用的内存空间(收集的是对象占用的内存空间而不是对象本身)。
java堆分为年轻代(Yong Generation)和老年代(Old Generation),年轻代又分为伊甸园区(Eden)和幸存区(Survivor),幸存区又分为From Survivor(S0)空间和To Survivor(S1)空间,参考上图。
新创建的对象存储在年轻代中,当年轻内存占满后,会触发Minor GC,清理年轻代内存空间。
老年代存储长期存活的对象和大对象。年轻代中存储的对象,经过多次GC后仍然存活的对象会移动到老年代中进行存储。老年代空间占满后,会触发FULL GC。
注:①Young GC(也叫Minor GC)发生在Eden、S0、S1区;Major GC发生在Old区;而FULL GC是清理整个堆空间,包括年轻代和老年代。
②如果FULL GC之后,堆中仍然无法存储对象,而且堆无法扩展,就会抛出OutOfMemoryError(OOM)错误。
(1)关于年轻代、老年代具体细节
默认的,年轻代和老年代空间大小比例为1:2,可通过参数-XX:NewRatio配置;
默认的,Eden:from:to=8:1:1,可通过参数-XX:SurvivorRatio配置;
Survivor区域中的对象被复制次数最大为15,可通过参数-XX:Max Tenuring Threshold配置。
(2)为什么年轻代要区分Eden区和Survivor区,为什么要设置两个Survivor区
①如果没有Survivor,Eden每进行一次Minor GC,存活的对象就会被送入老年代,老年代很快被填满,接着触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。由于老年代的空间远大于年轻代,每进行一次FULL GC消耗的时间比Minor GC长得多,影响程序执行、响应速度,所以要区分Eden区和Survivor;
②Survivor存在的意义,就是减少被送到老年代的对象,进而减少FULL GC的发生,Survivor的预筛选保证,只有在Survivor经历15次复制还能在年轻代存活的对象才会被送到老年代。
③设置两个Survivor的最大好处就是减少了空间碎片化。如果只有一块Survivor,就会产生下图这种情况(色块代表被对象占用了的空间),假设Eden满了,Survivor中有一部分对象,由于Eden满了所以触发Minor GC,Eden和Survivor中只有一部分存活对象,接着把Eden中存活对象转移到Survivor中,就会导致空间碎片化(碎片空间:Survivor中由于Minor GC死亡的对象占用的空间,也就是下图中“碎片化”指的地方)。
如果有两块Survivor(S0和S1),假设Eden满了,Survivor的S0中有一部分对象,由于Eden满了所以触发Minor GC,Eden中存活的对象以及S0中存活的对象会被复制到S1中(如果S1放不下就放到老年代中),Eden以及S0被清空。这样就保证了Survivor中不会空间碎片化。
2.虚拟机栈
线程私有,是描述java方法执行的内存模型。
用于存储局部变量表、操作数栈、动态链接、方法出口等信息。(存储临时变量、基本数据类型、对象的引用、方法出口等)
3.方法区
存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等(jdk7及以前称为永久代,jdk8后把它改为元数据空间)。回收目标主要是常量池的回收和类型的卸载,各线程共享。
常量池是方法区的一部分
JDK1.8 使用元空间 MetaSpace 替代方法区,元空间并不在 JVM中,而是使用本地内存。
4.程序计数器
当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址(存储程序当前运行的位置),线程私有。
为了线程切换可以恢复到正确执行位置,每个线程都需有独立的一个程序计数器,不同线程之间的程序计数器互不影响,独立存储。
如果线程执行 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是 Native 方法,计数器值为Undefined。
程序计数器这块内存区域是虚拟机规范中唯一没有OutOfMemoryError的区域。
5.本地方法栈
和虚拟栈相似,只不过它服务于Native方法,可能底层调用的c或者c++,线程私有。