一、JVM整体结构
JVM整体结构主要由三块组成:类装载子系统、字节码引擎、运行时数据区(内存模型)。 运行class文件时,首先由类装载子系统加class文件信息装载进运行时数据区,然后由字节码引擎来执行内存模型中的代码。
二、 JVM内存模型
栈:线程私有的一块区域,每创建一个线程就会从内存中分配一个区域来当作栈空间,其大小以-Xss指定,它的值一般为几百KB到1M。栈中的存储单位称为栈帧,线程每运行到一个方法都会创建一个与之对应的栈帧,用于存放该方法的局部变量表、操作数栈、动态链接和方式出口(方法返回地址),方法调用结束则栈帧销毁释放内存,因而栈帧符合栈数据结构的FILO(先进后出)特性,同时它也不会发生内存泄漏和GC,栈中存放的对象为对象的引用、基本类型的变量,栈的大小是编译期间可知的。
(1)局部变量表:用于存放当前方法局部变量的一个数组,变量0默认存的是方法的调用者this,往后依次存的局部变量。
(2)操作数栈:局部变量的值(操作数)在程序运行过程中用于做计算的一块临时存放内存空间,属于栈结构。
(3)动态链接:某些自定义方法的调用代码在编译时只会对当前方法做一个符号引用,在程序运行到这行代码时,会根据该符号引号找到相关代码在方法区的内存地址,这个直接引用称之为动态链接。即动态链接里存放的是方法代码在方法区的内存地址。
(4)方法出口:即方法的返回地址。存放的是当前方法在执行结束后上一层方法该执行哪一行信息。
本地方法栈: 本地方法栈与栈是相似的,也是线程私有的,不同的是它里面记录的本地方法调用的信息。
程序计数器:也称为PC计程器,它是线程私有的,记录的是当前线程运行的代码指令地址,只对java方法计数,如果运行到native方法则它的值为undefined,它是唯一一块不会发生内存泄漏的区域。
堆:线程共享的一块区域,用于存放new出来的对象、数组、实例变量。它是发生GC最频繁的一块区域,因此,堆也称为GC堆。堆的内存由-Xms指定,默认是物理内存的1/64;堆的最大内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70% 时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后自动调整堆的大小,引起性能的波动。堆按功能分可分为年轻代和老年代,默认比例1:2,年轻代可分为一个1个eden区和两个suvivor区。堆的这些区域在物理上都是连续的内存空间(从G1开始,往后的垃圾收集器就不是了)。新生成对象绝大多数都会先进入年轻代的eden区,eden区满后触发minor gc将存活的对象放入其中一个suvivor区,在下一次minor gc时又将eden和这个suvivor区存活的对象放入另一个suvivor区,如此往复。对象在suvivor区复制15次后会进入老年代,当老年代满时则触发full GC,fullGC的时间是minor gc的几十上百倍。故而对GC调优很大一部分就是减少full gc触发的频率。
对象进入老年代的条件:
(1) 新生成的大对象直接进入老年代,JVM参数 -XX:PretenureSizeThreshold 可以设置大 对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下 有效。
(2)长期存活的对象进入老年代,对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度 (默认为15岁,CMS收集器默认6岁,不同的垃圾收集器会略微有点不同),就会被晋升到老年代中。对象晋升到老年代 的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。
(3)对象动态年龄判断,如果一批对象放入suvivor区时,他们的总大小大于suvivor区的50%(-XX:TargetSurvivorRatio可以指定),则将偏大的对象直接放入老年代。对象动态年 龄判断机制一般是在minor gc之后触发的。
方法区:线程共享的一块区域,主要用于存放class类模型,静态变量的引用对象。在jdk1.8之前它 的具体实现称为永久代,用的是虚拟机中的内存空间,在jdk1.8及之后的版本它的具体实现称为元数据区,用的是本地内存。元空间的初始大小为21M,在使用的过程中它的大小会动态伸缩,而伸缩的过程中会触发full gc,因此在使用过程中通常会给方法区指定默认大小和最大大小,避免动态伸缩产生full gc影响性能。