简述 垃圾回收算法(下)

  • 时间:
  • 浏览:
  • 来源:互联网

前情提要,上一篇文章我们知道了 标记-清除算法,以及优化了碎片空间的问题而衍生的 标记-复制算法 和 标记-压缩算法。然而,这两种算法的优缺点都比较极端,因此实际应用中很少用到。

在文章的最后,提到了一种算法:分代算法,它能够比较好的平衡这两者的优缺点,将效率提升一个层次。那么,它到底是何方神圣,下面将带大家揭开它的庐山真面目,废话少说,开始发车!


什么是分代算法

首先,我们需要了解分代算法的空间结构,分代算法 沿袭了 标记-复制算法的分区的特色,将内存空间分为两个区域:新生代 和 老年代

新生代 又分为三个区域:eden区 和 Survivor区,其中 Survivor区 分为 from区 和 to区

默认情况下,这三个区域的空间比例是:4:2:1:1。

下面用一张图来简单展示 分代算法 的内存空间:

图1

为什么要这样子设计呢?简单来说,这是根据对象的生命周期特征,并且结合其他垃圾回收算法的优缺点。对象的生命周期符合以下两个特征:

(1)绝大部分对象的存活时间很短,很快就被回收。

(2)经历过多次垃圾回收过程的依然存活对象,下一次大概率依旧存活。


分代算法的回收过程

了解 分代算法 的空间结构后,接下来我们演示 分代算法 的整体回收过程。

新创建的对象都会先存放在 eden区,当 eden区满了,触发 新生代GC:

(1)对 eden区的对象进行标记

(2)将存活对象复制到 from区,存活对象的年龄+1

(3)清空 eden区

下面用 图2 和 图3 演示整个过程。

图2(新生代GC前)

图3(新生代GC后)


当 eden区再次满了,触发 新生代GC,然而,这次不会把存活对象复制到 from区,而是复制到 to区。因为,from区 和 to区 是交替使用的,这种方式正是 标记-复制算法 的特点。

新生代GC的过程如下:

(1)对 eden区 和 from区 的对象进行标记

(2)将存活对象复制到 to区,存活对象的年龄+1

(3)清空 eden区 和 from区

下面用 图4 和 图5 演示整个过程。

图4(新生代GC前)

图5(新生代GC后)


每次新生代GC后,依然在 from区 或 to区 存活的对象都会做一个标记:年龄+1,目的就是记录对象存活的次数,方便筛选出那些 稳定 的对象。默认配置下,当存活对象的年龄达到15后,将会分配到 老年代 区域。

因为,根据对象的生命周期特征:经历过多次垃圾回收过程的依然存活对象,下一次大概率依旧存活。那些经历过15次新生代GC依然存活的对象,我们可以认为它是稳定的,会继续存活,因此需要一个特殊的空间来进行管理。

新生代GC的过程如下:

(1)对 eden区 和 from区 的对象进行标记

(2)将年龄为15的存活对象复制到 老年代区域

(3)将存活对象复制到 to区,存活对象的年龄+1

(4)清空 eden区 和 from区

例如下 图6 和 图7 的存活对象 13,已经经历了15次新生代GC依然存活。

图6(新生代GC前)

图7(新生代GC后)

除此之外,在进行新生代GC的时候,eden区存在 超大对象,或者 from区/to区 容纳不下的对象,将会直接晋升存放到老年代区域。


经历多次新生代GC后,老年代总会有空间不足的一天,这时候就需要进行一次老年代的GC,腾出老年代区域的空间。对老年代区域进行垃圾回收,执行的方式正是我们熟悉的 标记-整理算法

老年代GC的过程如下:

(1)对 老年代区域 的对象进行标记

(2)将回收对象进行清除

(3)对 老年代区域 进行空间压缩

(4)对 eden区 和 from区 的对象进行标记

(5)将年龄为15的存活对象复制到 老年代区域

(6)将存活对象复制到 to区,存活对象的年龄+1

(7)清空 eden区 和 from区

下面用 图8 和 图9 演示整个过程。

图8(老年代GC前)

图9(老年代GC后)


为什么这样设计

从上面的 分代算法 回收过程的演示,我们可以发现,分代算法 比 标记-复制算法 和 标记-压缩算法 都复杂得多,根据不同的场景执行不同的操作,不只是单一的 复制 或者 压缩。

在 分代算法中,可以清晰看到 标记-复制算法 和 标记-压缩算法 的影子,例如新生代GC的回收方式就是 标记-复制算法;老年代GC垃圾回收的方式就是 标记-压缩算法。

那么,为什么这样设计呢?或者更加针对性地问,为什么 分代算法 要分为新生代和老年代呢?

首先,复制比压缩的执行效率要高得多,而且大部分对象的生命周期都是存活时间很短。将新生代分为 eden区、from区 和 to区,利用 标记-复制算法 争取最高效率来应对大部分对象的回收。

另一方面,压缩比复制的空间利用率高得多,适合应对相对稳定的场景。根据对象的生命周期特性,存活时间较长的对象是少数,它们相对稳定的存活在内存空间。因此,对于那些历经多次垃圾回收依然存活的对象,专门开辟一个空间,利用 标记-压缩算法 来进行管理总体效率是最高的。


再说一句

分代算法 正是将 标记-复制算法 和 标记-压缩算法 两者的优点发挥到合适的地方,扬长避短,并将他们巧妙的结合在一起,达到一种微妙的平衡,提升了整体垃圾回收的效率。这让我想到了牛顿的一句话 “如果说我看得比别人更远些,那是因为我站在巨人的肩膀上”。

本文链接http://metronic.net.cn/metronic/show-53451.html