OpenJDK 17 中的 Shenandoah:亚毫秒级 GC 停顿「译」

原文链接:https://mp.weixin.qq.com/s/AOWBC4AEMvrJaqoYDcX7aw

OpenJDK 17 中的 Shenandoah:亚毫秒级 GC 停顿「译」


一、前言

Shenandoah OpenJDK 垃圾收集(GC) 项目的主要动机是减少垃圾收集暂停时间。在 JDK 12 中,发布了原始的 Shenandoah 垃圾收集器,它实现了并发堆疏散,解决了在不停止应用程序的情况下清理(可能很大)堆的主要问题。这个版本最终被移植到 JDK 11。在 JDK 14 中,实现了并发类卸载,在 JDK 16 中,添加了并发引用处理,这两者都进一步减少了垃圾收集操作的暂停时间。暂停下剩余的垃圾收集操作是线程堆栈处理,已经在 JDK 17 中 解决了这个问题。

本文介绍了 Shenandoah GC 中新的并发线程堆栈处理。在 JDK 17 中并发处理线程堆栈为我们提供了可靠的亚毫秒级暂停。

二、Java中的线程处理

什么是线程处理,为什么我们需要为它停止应用程序?Java 程序在线程中执行,每个线程拥有一个栈:栈帧的列表,每个帧保存着局部变量、监视器以及与当前执行的方法相关的其他信息。最重要的是,在 Java 垃圾收集的上下文中,它保存对堆对象的引用(例如,引用类型化对象的局部变量)。

当垃圾收集周期开始时,我们首先扫描所有线程的堆栈,以使用我们在堆栈上找到的引用来播种标记队列。我们在GC 暂停(安全点)时这样做,因为我们需要在标记开始时堆栈的一致状态,而不是线程的执行并发地与堆栈混淆。完成后,我们继续执行并遍历可达对象的图,从我们在初始线程扫描期间找到的引用开始。

同样,当将可到达的对象疏散到空区域时,我们需要更新线程堆栈上的所有引用以指向新的对象位置。我们需要暂停一下,因为垃圾收集加载屏障通常在从堆加载引用时起作用(例如加载到局部变量或寄存器中),这意味着局部变量或寄存器在需要 GC 的状态下不能有对象引用干涉。到那时,通过垃圾收集屏障为时已晚。为每个局部变量或寄存器访问快速调用垃圾收集屏障会遇到性能问题。

扫描和处理线程堆栈需要时间。小型工作负载(具有小堆栈的少量线程)可能只需要几毫秒来扫描,但是大型工作负载——应用程序服务器,我正在看着你!——很容易花费几十毫秒来处理。所有这些处理都是在应用程序停止时完成的,因此它会影响应用程序的整体端到端延迟。

三、OpenJDK 17 中的并发线程处理

我们如何改善这种情况并同时处理线程堆栈?我们通过使用一种称为堆栈水印的机制(最初由 ZGC 开发人员实现)来实现这一点。中心观察是所有线程堆栈的操作都发生在最顶层的框架中:当前执行的方法。下面的所有帧基本上都是静态的并且不会改变——它们可以被垃圾收集线程安全地并发扫描。我们需要做的就是在堆栈帧被销毁时(例如,通过返回调用者,或通过抛出异常)协调 GC 线程与正在执行的线程,从而退出 GC 处理。这种协调是通过堆栈水印实现的,一个告诉我们堆栈的哪些部分可以安全扫描的指针,以及一个允许垃圾收集器处理返回的屏障。图 1 说明了堆栈水印在并发线程处理中的作用。

OpenJDK 17 中的 Shenandoah:亚毫秒级 GC 停顿「译」

并发线程处理中的堆栈水印。

四、在垃圾收集期间使用堆栈水印

让我们考虑一个例子。比如说,在标记开始时,在初始暂停期间,我们将堆栈水印设置为每个线程的最顶层帧并武装线程。这意味着我们认为所有帧对于并发扫描都是安全的,但没有(还)可以执行。从安全点返回后,我们将控制权交还给 Java 程序,因此需要顶层框架才能安全执行。在这里,堆栈水印屏障开始发挥作用,让垃圾收集器处理顶部帧(出于实际原因,还有它的调用者)。线程将扫描顶部帧并相应地降低水印,并在安全点之前离开的点恢复自己的执行。同时,GC 线程也开始扫描堆栈,从底部向上到水印,即在安全区中。

  1. 将水印降低一帧。
  2. 防止 GC 线程扫描超出该水印。
  3. 通过扫描任何引用来处理现在位于水印上方的帧。

最终结果是我们将有效地扫描所有相同的帧和引用,就像我们在初始标记暂停时所做的那样,但我们是在程序执行时同时进行的。

五、Shenandoah GC 基准测试

那么这些变化在实践中会产生什么影响呢?我已经运行了许多衡量垃圾收集暂停的基准测试。下表显示了 JDK 11、JDK 16 和 JDK 17 中所有基准测试的平均暂停时间。JDK 16 和 JDK 17 之间的差异显示了并发堆栈处理所实现的改进。与 JDK 11 的区别是为了完整性而显示的,包括与以前版本相比的各种其他改进。

OpenJDK 17 中的 Shenandoah:亚毫秒级 GC 停顿「译」

六、发行版

Shenandoah 的可用性因供应商和 JDK 版本而异。默认情况下,OpenJDK 12+ 构建通常包括 Shenandoah。OpenJDK 11 需要在构建时选择加入。

已知供应商状态为:

七、启用 Shenandoah

使用 -XX:+UseShenandoahGC JVM 选项通过 Shenandoah GC 运行您的 Java 应用程序。

java  -XX:+UseShenandoahGC

7.1 模式

模式定义了 Shenandoah 多运行的主要方式。并定义了主要的性能特征。可以使用 -XX:ShenandoahGCMode=选择模式 。可用模式有:

  1. normal/satb(默认)。此模式使用 Snapshot-At-The-Beginning (SATB) 标记运行并发 GC。这种标记模式类似于 G1 所做的:通过“前一个”对象拦截写入和标记。
  2. iu(实验性)。此模式使用增量更新 (IU) 标记运行并发 GC。这种标记模式是SATB 模式的镜像:通过“新”对象拦截写入和标记。这可能会使标记不那么保守,尤其是在访问弱引用方面。
  3. passive(诊断)。此模式运行 stop-the-world GC。此模式用于功能测试,但有时它对于用 GC 屏障平分性能异常或计算应用程序中的实际实时数据大小很有用。

7.2 基本配置

基本配置和命令行选项:

在启用日志记录的情况下运行几乎总是一个好主意。这个汇总表传达了有关 GC 性能的重要信息,我们几乎不可避免地会在性能错误报告中要求提供。启发式日志对于找出 GC 异常值很有用。

其他推荐的 JVM 选项是:

八、结论

本文解释了 Shenandoah GC 中的并发线程堆栈处理如何解决剩余垃圾收集暂停时间问题并在 JDK 17 中提供可靠的亚毫秒级垃圾收集暂停。要了解更多信息,请访问 Shenandoah GC 项目的 GitHub 存储库 和 OpenJDK Wiki 页面。

展开阅读全文

页面更新:2024-03-15

标签:堆栈   水印   屏障   线程   停顿   变量   局部   应用程序   标记   对象   内存   性能   垃圾   版本   模式   时间   科技

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top