分代垃圾回收算法
现代优秀的垃圾回收算法,会将上述描述的垃圾回收算法组合进行使用,其中应用最广的就是分代垃圾回收算法(Generational GC)。
1. 分代组成
分代垃圾回收将整个内存区域划分为年轻代和老年代:
1.1 arthas查看分代之后的内存情况
在JDK8中,添加-XX:+UseSerialGC
参数使用分代回收的垃圾回收器,运行程序。在arthas中使用memory命令查看内存,显示出三个区域的内存情况。
2. 调整内存区域的大小
根据以下虚拟机参数,调整堆的大小。注意加上-XX:+UseSerialGC
打印日志的话,写了
-XX:+PrintGCDetails
就可以不用再写verbose:gc
。为了没有控制老年代大小的参数呢?因为可以通过-Xms
、-Xmx
、-Xmn
三个参数进行指明。
// -XX:+UseSerialGC -Xmx60m -Xms60m -Xmn20m -XX:SurvivorRatio=3
public class Jvm12 {
public static void main(String[] args) throws IOException {
System.in.read();
}
}
启动后,在arthas中查看:
3. 分代垃圾回收流程
刚创建出来的对象,首先会被放入Eden伊甸园区。
随着对象在Eden区越来越多,如果Eden区满,新创建的对象已经无法放入,就会触发年轻代的GC,称为Minor GC或者Young GC。
Minor GC会把需要eden中和From需要回收的对象回收,把没有回收的对象放入To区。 接下来,S0会变成To区,S1变成From区。当eden区满时再往里放入对象,依然会发生Minor GC。此时会回收eden区和S1(from)中的对象,并把eden和from区中剩余的对象放入S0。
注意:每次Minor GC中都会为对象记录他的年龄,初始值为0,每次GC完加1。
如果Minor GC后对象的年龄达到阈值(最大15,默认值和垃圾回收器有关),对象就会被晋升至老年代。比如年轻代里面存储满了,这时候就会把一些年龄没有达到15的对象放到老年代里面。 当老年代中空间不足,无法放入新的对象时,先尝试minor gc如果还是不足,就会触发Full GC,Full GC会对整个堆进行垃圾回收。
为了老年代空间不足不进行回收老年代呢?主要是因为年轻代里面可能此时放入的对象年龄就是不足15的,minor gc后就没有需要放入到老年代的了(对象年龄只有继续增长)。当然这不一定能百分百解决问题,如果Full GC就是造成STW, 对程序运行造成影响。
如果Full GC依然无法回收掉老年代的对象,那么当对象继续放入老年代时,就会抛出Out Of Memory异常。
// -XX:+UseSerialGC -Xmx60m -Xms60m -Xmn20m -XX:SurvivorRatio=3 -XX:+PrintGCDetails
public class Jvm12 {
public static void main(String[] args) throws IOException {
List<Object> list= new LinkedList<>();
int count = 0;
while(true){
System.in.read();
System.out.println(++count);
// 每次添加2M数据
list.add(new byte[1024*1024*2]);
}
}
}
每次回车都会执行添加向堆空间里面2M数据,回车第5次的时候,发现出现了GC,然后我们停止程序,它会打印堆信息信息到控制台: 可以发现此时老年代就已经有了部分数据,这就是之前提到的新生代满了会提前放到老年代中。但发生了GC,此时新生代中的伊甸园区应该为空的才对,原因是在第五次放入数据的时候,发现JVM正在GC,GC完毕后还需要把数据放到伊甸园区。
再次启动程序,一直回车:
4. 堆内存分代的意义
可能你会问为什么分代GC算法要把堆分成年轻代和老年代?
4.1 背景
比如在web开发场景中,数据可以分为两大类:
系统中的大部分对象,都是创建出来之后很快就不再使用可以被回收,比如用户获取订单数据,订单数据返回 给用户之后就可以释放了。
还有一部分是会存放长期存活的对象,比如Spring的大部分bean对象,在程序启动之后就不会被回收了。
4.2 意义
- 可以通过调整年轻代和老年代的比例来适应不同类型的应用程序,提高内存的利用率和性能。
- 新生代和老年代使用不同的垃圾回收算法,新生代一般选择复制算法,老年代可以选择标记-清除和标记-整理 算法,由程序员来选择灵活度较高。
- 分代的设计中允许只回收新生代(minor gc),如果能满足对象分配的要求就不需要对整个堆进行回收(full gc),STW时间就会减少。