深入探索JVM垃圾回收:ARM服务器垃圾回收的挑战和优化
上QQ阅读APP看书,第一时间看更新

2.3.4 分代回收

3种GC算法各有优缺点,实际中需要根据需求选择不同的实现。除此以外还可以将内存空间划分成多个区域,每个区域采用一种或者多种算法协调管理。这个思路来自人们对应用程序运行时的观察和分析。根据研究发现,大多数应用运行时分配的内存很快会被使用,然后就释放,这意味着为这样的对象划分一块内存空间,然后使用复制算法效率会很高,因为对象的生命周期很短,在GC执行时大多数对象都已经死亡,只需要标记/复制少量的对象就可以完成内存回收。现代垃圾回收实现中都会根据对象的生命周期划分将内存划分成多个代进行管理,最常见的是将内存划分为两个代:新生代和老生代,其中新生代主要用于应用程序对象的分配,一般采用复制算法进行管理;老生代存储新生代执行GC后仍然存活的对象,一般采用标记清除算法管理。

基于对象生命周期管理,有弱分代理论假设和强分代理论假设两种:

1)弱分代理论假设:假定对象分配内存后很快使用,并且使用后很快就不再使用(内存可以释放)。

2)强分代理论假设:假定对象长期存活后,未来此类对象还将长期存活。

基于弱分代理论将内存管理划分成多个空间进行管理,基于强分代理论可以优化GC执行的效率,不回收识别的长期存活对象,从而加快GC的执行效率。

值得一提的是,目前弱分代理论在高级语言中普遍得到证实和认可,但是对于强分代理论只在一些场景中适用。目前弱分代理论和强分代理论在JVM中均有体现。

虽然分代回收的思想非常简单,但实现中有许多细节需要考虑,例如在内存分代以后,分代边界是否可以调整?以内存划分为两个代为例,最简单的实现是边界固定,如图2-10所示。

图2-10 边界固定的分代划分

边界固定的分代回收算法实现简单,可以通过固定边界快速判断对象处于哪个空间,管理代际引用也比较简单。但是边界固定的分代方法需要JVM使用者提前设定好每个代的大小,这对于JVM使用者来说并不容易,实际使用中可能需要使用者不断调整边界,以便内存代的划分和内存使用方式一致。

一种很自然的优化是将边界设计为浮动的,浮动可以解决使用者需要分代划分的问题,由JVM根据程序使用内存的情况自动调整内存代的划分。边界浮动的示意图如图2-11所示。

图2-11 边界浮动的分代划分

边界浮动后可以缩小新生代也可以扩大新生代,一般来说缩小新生代会导致GC的停顿时间减少、吞吐量减少,如图2-12所示。而扩大新生代会导致GC的停顿时间增加、吞吐量增加,如图2-13所示。

图2-12 边界浮动之缩小新生代

图2-13 边界浮动之扩大新生代

浮动边界对JVM使用者很友好,但是回收算法的实现难度增加了很多。在JVM中所有的垃圾回收器实现中只有一款实现了边界浮动,但该功能因为存在一些bug,已在JDK 15中被移除,关于如何实现边界浮动将在第5章详细介绍。

除了代际边界划分的问题,在分代中还需要考虑分代的大小、代际引用管理等问题。这些问题将在后续具体垃圾回收器的实现中介绍。