跳到主要内容

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

概括

为 G1 垃圾收集器的巨型对象功能开发额外的白盒测试。

非目标

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

描述

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

G1 对待大于内存区域一半的对象(称为_巨型对象)_,与其他对象不同:

  • 巨大的物体总是占据多个区域。如果一个巨大的物体小于一个区域,那么它会占据整个区域。如果一个巨大的对象大于 N 个区域且小于 (N+1) 个区域,那么它会占用 (N+1) 个区域。最后一个区域的可用空间(如果有)中不允许进行分配。

  • 它们只能在并发标记周期结束时、在完整 GC 期间或在 G1 Eager Reclaim 的情况下在年轻 GC 中收集

  • 它们永远不能从一个地区转移到另一地区。

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

为了测试处理巨型对象的代码是否按预期工作,我们需要 G1 提供有关堆上巨型对象的内部表示的更多详细信息。我们将为 G1 添加额外的调试方法,这将使我们能够从其内部数据结构获取信息并提供对垃圾收集启动的控制。后者很重要,因为有三个代码路径可以收集无法访问的巨大对象:完全垃圾收集、并发标记和 G1 Eager Reclamation 情况下的年轻 GC。为了测试每条路径,我们需要避免另一条路径。

为了帮助解决这个问题,我们将通过以下方式扩展 WhiteBox API:

  • 阻止和启动并发标记和完整 GC 的方法。

  • 枚举 G1 区域和访问区域属性(例如,空闲/占用/巨大)的方法。

  • 访问内部 G1 变量(例如可用内存、区域大小和可用区域数量)的方法。

  • 定位堆中区域的方法,以检查属于巨大对象的区域中是否没有发生分配。 (这可能是“堆遍历器”API 的第一步,它允许我们完全迭代 Java 堆)。

备择方案

可能的替代方案是:

  • 本机内置 JVM 测试。此类测试可以使用 JVM 标志来启动。它们不适合,因为测试失败可能会导致崩溃。 JVM 应该能够在测试失败后继续工作,但这种方法不能保证这一点。

  • 本机测试。这需要向 G1 代码添加调试方法,并且实际上需要开发本机 WhiteBox API。这种方法有一定的缺点:我们无法使用这些调试方法进行压力测试。然而更重要的是,仍然没有原生的测试框架。

风险和假设

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