跳到主要内容

JEP 278:G1 中超大对象的附加测试

QWen Max 中英对照

概述

为 G1 垃圾收集器的超大对象(Humongous Objects)功能开发额外的白盒测试。

非目标

我们不会为 G1 Eager Reclamation 开发测试。

描述

Garbage First(G1)是一种分代垃圾收集器,它将堆划分为大小相等的区域。G1 具有一个并发收集阶段,可以与应用程序并行工作,并且是多线程的。

G1 对大于半个内存区域的对象(称为 超大对象)与其他对象的处理方式不同:

  • 庞大的对象总是占用多个区域。如果一个庞大对象小于一个区域,则它将占用整个区域。如果一个庞大对象大于 N 个区域且小于 (N+1) 个区域,则它将占用 (N+1) 个区域。在最后一个区域的空闲空间中不允许有任何分配。

  • 它们只能在并发标记周期结束时、在完整 GC 期间或在 G1 渴望回收的情况下在年轻代 GC 中被收集。

  • 它们永远不能从一个区域移动到另一个区域。

由于 G1 是一个并发且多线程的垃圾回收器(GC),这使得黑盒测试变得非常困难。多种收集死对象的方式、若干并发线程、能够与正在运行的应用程序并行工作,以及通常较为复杂的算法,几乎让人无法弄清 G1 的内部状态。为了解决这些问题,我们将扩展 WhiteBox API,并实现使用该 API 的 Java 测试,以检查 G1 的内部状态。我们还能够在压力测试中重复使用这些新开发的 WhiteBox API 方法。

为了测试处理超大对象的代码是否按预期工作,我们需要 G1 提供更多关于堆中超大对象内部表示的详细信息。我们将在 G1 中添加额外的调试方法,这些方法将允许我们从其内部数据结构中获取信息,并提供对垃圾回收启动的控制。后者之所以重要,是因为有三种代码路径可以回收不可达的超大对象:完全垃圾回收、并发标记,以及在 G1 急切回收(G1 Eager Reclamation)情况下的年轻代垃圾回收。为了测试每条路径,我们需要避免其他路径的干扰。

为了对此提供帮助,我们将通过以下方式扩展 WhiteBox API:

  • 用于阻止和启动并发标记及完全垃圾回收(GC)的方法。

  • 用于枚举 G1 的区域并访问区域属性(例如,空闲/占用/超大对象)的方法。

  • 用于访问 G1 内部变量的方法,例如空闲内存、区域大小和空闲区域的数量。

  • 用于在堆中定位区域的方法,以检查在属于超大对象的区域中是否没有分配发生。(这可能是实现“堆遍历器” API 的第一步,该 API 可让我们完全遍历 Java 堆)。

替代方案

可能的替代方案是:

  • 原生内置的 JVM 测试。这类测试可以通过一个 JVM 标志来启动。但它们并不合适,因为测试失败很可能会导致崩溃。JVM 应该在测试失败后能够继续运行,而这种方法无法保证这一点。

  • 原生测试。这需要在 G1 代码中添加调试方法,并且实际上要开发一个原生的 WhiteBox API。这种方法存在一些缺点:我们无法将这些调试方法用于压力测试。更重要的是,目前仍然没有原生的测试框架。

风险与假设

新的测试可能需要对 G1 进行更改。这可能会影响 G1 的性能和稳定性,尽管我们认为这不太可能。如果 G1 受到负面影响,那么我们可以构建不包含调试方法的产品二进制文件。