我知道大家经常会被问这样的面试题-对象在什么情况情况下会进入老年代,答案我想大家都是知道的,但是更详细一点的,到底是年龄到了15就进入老年代呢,还是15的下一次young GC进入老年代,最大年龄设置为0会是什么情况,设置为16又会是什么情况?让我们用代码来实际操作看下。
1)动态年龄判断,当幸存区中年龄1+年龄2+年龄3+...+年龄n-1<50%,年龄1+年龄2+年龄3+...+年龄n-1+年龄n>50%,那么幸存区中年龄n及其以上的对象就会放到老年代中。
2)年龄到达阈值,通过-XX:MaxTenuringThreshold进行设置,默认为15
3)大对象,因为程序一般认为大对象有可能是需要存活比较长时间的对象,而且大对象在幸存区返回来回复制,影响young GC的效率,通过-XX:PretenureSizeThreshold进行设置。
实战演练
首先是JVM配置
-XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=5242880 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
相关配置释意:新生代 10M 、伊甸区 8M、幸存区1 1M、幸存区2 1M、堆 20M、老年代 10M、年龄阈值是15、大对象是 10M;使用ParNewGC+CMS;打印相关gc日志到gc.log文件
1)动态年龄判断
public static void main(String[] args) {
byte[] array1 = new byte[2*1024*1024];//2M
array1 = new byte[2*1024*1024];//2M
array1 = new byte[2*1024*1024];//2M
array1 = null;
array1 = new byte[2*1024*1024]; //第一次younggc 把初始对象放置到幸存区 ,大小为562K,比例超过50%,年龄为1
}
执行以上代码,在执行到最后一行的时候,伊甸区内存不够,导致了首次young GC,562K的初始化对象放置于幸存区,年龄为1
public static void main(String[] args) {
byte[] array1 = new byte[2*1024*1024];//2M
array1 = new byte[2*1024*1024];//2M
array1 = new byte[2*1024*1024];//2M
array1 = null;
array1 = new byte[2*1024*1024]; //第一次younggc 把初始对象放置到幸存区 ,比例超过50%,年龄为1
array1 = new byte[2*1024*1024];//2M
array1 = new byte[2*1024*1024];//2M
array1 = new byte[2*1024*1024];//第二次younggc 把初始对象全部移到老年代
}
执行以上代码,在执行到最后一行的时候,伊甸区内存不够,导致了第二次young GC,562K的初始化对象放置于幸存区(初始化对象可以会有部分变化),年龄为1,因为年龄为1的对象>50%,所以562K的对象全都放到老年代。
2)年龄到达阈值
public static void main(String[] args) {
// 1024*1024 是 1M
byte[] array1 = new byte[2*1024*1024];
array1 = new byte[2*1024*1024];
array1 = new byte[2*1024*1024];
array1 = new byte[128*1024];
array1 = null;
array1 = new byte[2*1024*1024]; //第一次younggc 把初始对象放置到幸存区 ,比例超过50%,年龄为1
array1 = new byte[2*1024*1024];
array1 = new byte[2*1024*1024];
array1 = null;
byte[] array2 = new byte[128*1024];
array1 = new byte[2*1024*1024]; //第二次younggc 把初始对象放置到老年代 ,array2放置到幸存区,年龄为1
array1 = new byte[2*1024*1024];
array1 = new byte[2*1024*1024];
array1 = new byte[128*1024];
array1 = null;
array1 = new byte[2*1024*1024];//第三次younggc,array2仍然在幸存区,年龄为2
array1 = new byte[2*1024*1024];
array1 = new byte[2*1024*1024];
array1 = new byte[128*1024];
array1 = null;
int i = 0;
while(i<15) {
array1 = new byte[2*1024*1024];//第三到十八次younggc,array2仍然在幸存区,年龄为3-15
array1 = new byte[2*1024*1024];
array1 = new byte[2*1024*1024];
array1 = new byte[128*1024];
array1 = null;
i++;
}
}
以上gc过程中, array2 对象是在首次young GC之后一直存活的,在第二次young GC进入老年代,年龄为1,第16次(array2 经历的第15次)young GC时,array2仍然在老年代中,此时年龄为15,第17次(array2 经历的第16次)young GC,array2进入老年代。
如果-XX:MaxTenuringThreshold=16,启动会是怎么样的?
因为对象头用于表示年龄的长度就是4个bit,范围就会0~15,所以16是无效设置,所以那些说阈值设置大一点,就是在幸存区待的久一点的,是不对的,自己动动手,不信谣,不传谣。
如果-XX:MaxTenuringThreshold=0,启动会是怎么样的?
所有对象在young GC时,跳过幸存区,直接进入老年代。
3)大对象
public static void main(String[] args) {
byte[] array1 = new byte[6*1024*1024];//6M
}
由gc日志可知,没有 young GC一次,老年代已经使用了6M左右,这个就是大对象直接进入老年代