6.2 JVM优化垃圾回收的方式

对第 1 章介绍的软件来说,运行时会对其做些处理,其中一个很好的例子是弱代假设(Weak Generational Hypothesis,WGH)。简单来说,在这个假设中,对象常常处于少数几个预期生命周期之一(这些预期生命周期叫“代”)。

一般来说,对象的生命期非常短(有时把这种对象叫瞬时对象),不久就会当作垃圾回收。然而,有些少量对象会存在得久一点,因此注定会成为程序长期状态的一部分(程序的长期状态有时称为程序的工作集)。这种现象可通过图 6-2 表示,这幅图绘制的是预期生命周期中内存用量的变化。

{%}

图 6-2:弱代假设

这种变化趋势不是由静态分析推知的,在监测软件运行时行为时可以看出,在不同的应用场景中,这明显都是事实。

HotSpot JVM 有一个垃圾回收子系统,专门利用弱代假设。本节,我们要讨论如何在生命期短的对象(大多数情况下对象的寿命都短)上使用这些技术。这些论述默认针对 HotSpot,不过其他服务器端使用的 JVM 一般也都使用类似或相关的技术。

最简单的分代垃圾回收程序只会留意弱代假设,因为分代垃圾回收程序认为,较之充分使用弱代假设,做额外的簿记来监控内存,投入少收效大。在最简单的分代垃圾回收程序中,往往只有两代,这两代一般称为新生代和老年代。

筛选回收

在上述标记清理算法的清理阶段,一个一个回收对象,然后把各个对象占用的空间放回可用内存列表。然而,如果弱代假设成立,而且在任何一个垃圾回收循环中大多数对象都已“死亡”,那么使用另一种方式回收空间似乎更合理。

新的回收方式把堆内存分成多个独立的内存空间,每次回收垃圾时,只为活性对象分配空间,并把这些对象移到另一个内存空间。这个过程叫作筛选回收(evacuation),执行这个过程的回收程序叫作筛选回收程序。这种回收程序回收完毕后会清理整个内存空间,供以后重复使用。图 6-3 展示了筛选回收程序的工作方式。

{%}

图 6-3:筛选回收程序

这种回收方式可能比上述简单的回收方式高效得多,因为根本不用管已死对象。垃圾回收循环执行的时间长短和活性对象的数量成正比,而不是已分配空间的对象数量。这种回收方式唯一的缺点是,要稍微花点儿时间簿记,因为要复制活性对象,但这点儿时间与获得的巨大收益相比不值一提。

6.2 JVM优化垃圾回收的方式 - 图3 HotSpot 完全在用户空间中自行管理 JVM 堆,而且分配内存和释放内存时不用执行系统调用。对象一开始在 Eden 区(或叫 Nursery 区)创建,大多数生产环境使用的 JVM(至少 SE/EE 使用的 JVM)都会使用筛选回收策略回收 Eden 区的垃圾。

使用筛选回收程序的话,每个线程都可以单独分配内存。也就是说,每个应用线程都有一块连续的内存(叫线程私有的分配缓冲区),专门供这个线程分配新对象。为新对象分配内存时,只需把指针指向分配缓冲区,非常省事。

如果对象在回收操作即将开始之前创建,那么这个对象没有时间完成使命,在垃圾回收循环开始前就会“死亡”。在只有两代的回收程序中,这种生命期短的对象会被移入长存区,几乎相当于宣布“死刑”,然后等待下次回收循环将其回收。因为这种情况很少见(往往也很费事),执行上述操作似乎相当浪费资源。

为了缓和这种浪费,HotSpot 引入了 Survivor 区。Survivor 区用于保存前一次回收新生对象后存活下来的对象。筛选回收程序会在多个 Survivor 区之间来回复制存活下来的对象,直到超过保有阈值后,再把这些对象给老年代。

详述 Survivor 区和调校垃圾回收程序的方式已经超出本书范畴。如果要把应用部署到生产环境,应该参阅专门的资料。