Skip to content

分代垃圾回收算法

现代优秀的垃圾回收算法,会将上述描述的垃圾回收算法组合进行使用,其中应用最广的就是分代垃圾回收算法(Generational GC)。

1. 分代组成

分代垃圾回收将整个内存区域划分为年轻代和老年代: Alt text

1.1 arthas查看分代之后的内存情况

在JDK8中,添加-XX:+UseSerialGC参数使用分代回收的垃圾回收器,运行程序。在arthas中使用memory命令查看内存,显示出三个区域的内存情况。
Alt text

2. 调整内存区域的大小

根据以下虚拟机参数,调整堆的大小。注意加上-XX:+UseSerialGCAlt text 打印日志的话,写了-XX:+PrintGCDetails就可以不用再写verbose:gc。为了没有控制老年代大小的参数呢?因为可以通过-Xms-Xmx-Xmn三个参数进行指明。

java
// -XX:+UseSerialGC -Xmx60m -Xms60m  -Xmn20m  -XX:SurvivorRatio=3
public class Jvm12 {
    public static void main(String[] args) throws IOException {
        System.in.read();
    }
}

启动后,在arthas中查看:
Alt text

3. 分代垃圾回收流程

刚创建出来的对象,首先会被放入Eden伊甸园区。
随着对象在Eden区越来越多,如果Eden区满,新创建的对象已经无法放入,就会触发年轻代的GC,称为Minor GC或者Young GC。
Minor GC会把需要eden中和From需要回收的对象回收,把没有回收的对象放入To区。
Alt text 接下来,S0会变成To区,S1变成From区。当eden区满时再往里放入对象,依然会发生Minor GC。此时会回收eden区和S1(from)中的对象,并把eden和from区中剩余的对象放入S0。
Alt text 注意:每次Minor GC中都会为对象记录他的年龄,初始值为0,每次GC完加1。
如果Minor GC后对象的年龄达到阈值(最大15,默认值和垃圾回收器有关),对象就会被晋升至老年代。比如年轻代里面存储满了,这时候就会把一些年龄没有达到15的对象放到老年代里面。 Alt text 当老年代中空间不足,无法放入新的对象时,先尝试minor gc如果还是不足,就会触发Full GC,Full GC会对整个堆进行垃圾回收。
为了老年代空间不足不进行回收老年代呢?主要是因为年轻代里面可能此时放入的对象年龄就是不足15的,minor gc后就没有需要放入到老年代的了(对象年龄只有继续增长)。当然这不一定能百分百解决问题,如果Full GC就是造成STW, 对程序运行造成影响。
如果Full GC依然无法回收掉老年代的对象,那么当对象继续放入老年代时,就会抛出Out Of Memory异常。

java
// -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,然后我们停止程序,它会打印堆信息信息到控制台:
Alt text 可以发现此时老年代就已经有了部分数据,这就是之前提到的新生代满了会提前放到老年代中。但发生了GC,此时新生代中的伊甸园区应该为空的才对,原因是在第五次放入数据的时候,发现JVM正在GC,GC完毕后还需要把数据放到伊甸园区。
再次启动程序,一直回车:
Alt text

4. 堆内存分代的意义

可能你会问为什么分代GC算法要把堆分成年轻代和老年代?

4.1 背景

比如在web开发场景中,数据可以分为两大类:
系统中的大部分对象,都是创建出来之后很快就不再使用可以被回收,比如用户获取订单数据,订单数据返回 给用户之后就可以释放了。
还有一部分是会存放长期存活的对象,比如Spring的大部分bean对象,在程序启动之后就不会被回收了。

4.2 意义

  1. 可以通过调整年轻代和老年代的比例来适应不同类型的应用程序,提高内存的利用率和性能。
  2. 新生代和老年代使用不同的垃圾回收算法,新生代一般选择复制算法,老年代可以选择标记-清除和标记-整理 算法,由程序员来选择灵活度较高。
  3. 分代的设计中允许只回收新生代(minor gc),如果能满足对象分配的要求就不需要对整个堆进行回收(full gc),STW时间就会减少。
    Alt text