[译] 垃圾收集器算法--ZGC
原文地址: The Z Garbage Collector algorithm

原文作者: Jesús Navarrete

译者: maybelence

介绍

ZGC 最早作为 JDK11 中的预览特性发布, 去年 9 月 15 号,
随着 JDK 15 的正式发布,也带来了 ZGC 的正式版本。

ZGC 是可伸缩的低延迟垃圾收集器,最大 GC 暂停时间为 10 毫秒,能够处理从几兆字节到几 TB 的堆,最大吞吐量降低了 15%。

JVM 垃圾收集器

截止目前,JVM 已经引入了一系列有趣的垃圾收集算法。下面列出了几个最重要的垃圾收集算法:

串行(低内存占用):使用单线程来工作,适用于单处理器计算机,并且针对内存不足(嵌入式系统)进行了优化。 并行(吞吐量收集器):并行进行次要收集,以减少垃圾收集的开销。适用于多处理器硬件上运行的中型到大型数据集应用程序。 CMS(并发标记扫描收集器):具有较短的垃圾收集器暂停时间。专为具有大量长寿命对象或大量使用期限的应用程序而设计。 G1(吞吐量/等待时间平衡):Garbage-First 是服务器样式的垃圾收集器,适用于具有大内存的多处理器计算机。它试图以高概率满足 GC 暂停时间目标,同时实现高吞吐量。全堆操作(例如全局标记)与应用程序线程同时执行。这样可以防止与堆或活动数据大小成比例的中断。 ZGC(低延迟)
串行和并行称为 stop of the world 算法。 CMS 在 JDK 9 中已弃用,用 G1 代替。

这里要强调 ZGC 可以并行处理所有繁重的操作,而其他算法却无法做到(具体细节见下文)。

深入了解 ZGC

ZGC 是一种并发的低延迟算法,除了线程堆栈扫描外,它所有其他操作(标记,压缩,参考处理,重定位集选择,StringTable 清理,JNI WeakRef 清理,JNI GlobalRefs 扫描和类卸载)都是并行的。所以该算法对于低延迟确实非常有用。

ZGC 暂停时间并不随堆或活动大小而增加,而是与根集合的一个子集的大小相关(您的应用程序正在使用的Java线程数)。也就是仍然在 Stop-The-World 阶段扫描线程栈。但是从 JDK 16 开始,对线程栈的扫描是并行处理的,也就是说在扫描栈的时候应用程序可以同时运行。

原作者写的时候是 JDK15 ,这里我补充了一下 JDK 16 对 ZGC 的变化

从算法的角度来看,它是一个并发收集器,它在 Java 线程继续执行的同时完成了所有繁重的工作。它是一个基于区域的收集器,这意味着将堆划分为较小的区域,并且压缩工作将集中于这些区域的子集,即那些垃圾最多的区域。它是 NUMA感知 的,由于 CPU 具有本地内存,因此可以减少延迟。它使用彩色指针和负载屏障,将在以下各节中进行详细介绍。而且它是一个单一的一代收集器,它没有以前回收机制的年轻代或老年代。

ZGC 阶段

ZGC 的 GC 周期分为三个阶段。

在第一阶段(暂停标记开始)中,ZGC遍历对象图以将对象标记为活动或无用。此阶段还包括重新映射实时数据。

第二阶段是“暂停标记结束”,此阶段完成参考预处理。在该阶段还完成了类卸载和重定位集的选择。

暂停重定位启动是最后一个阶段,在此阶段要进行大量的压缩堆工作。

彩色指针

这是 ZGC 中的核心设计概念。该算法使用 64 位对象指针中的一些未使用的位来存储一些元数据,从而可以查找,标记,定位和重新映射对象。下图显示了 64 位对象指针和每个位的含义。

内存屏障

它是 JAVA 即时编译器在某些重要位置注入的代码。目的是检查加载的对象引用是否具有不良的颜色。当线程从堆中加载对象引用时,将运行负载屏障代码。

调优选项

从 JDK 11 到 JDK 15 发行版,如果要使用 ZGC 算法,必须解锁实验选项:

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

如果是 JDK 15 之后的版本,只需要指定一下内容:

-XX:+UseZGC

ZGC 的设计易于调整。下面是特定的 ZGC 选项的列表:

为了知道使用的时间并查看有关算法行为的一些数字,我们可以打印一些垃圾收集器日志,选择 ZGC 来查看简单日志时,只需添加以下命令即可:

-XX:+UseZGC -Xmx<size> -Xlog:gc

如果您想打印带有更多详细信息的垃圾收集器日志,可以执行以下操作:

-XX:+UseZGC -Xmx<size> -Xlog:gc*

接下来让我们看看其他有趣的调优选项。

设置堆大小

ZGC 中最重要的调整选项之一是设置最大堆大小 (-Xmx<size>) 。我们必须为我们的应用程序找到正确的值,因为我们不想丢失内存,并且希望在 GC 运行时允许我们的应用程序有足够的空间用于活动对象和分配。以下是使用示例:

-XX:+UseZGC Xmx<size> 

设置 GC 并发线程

尽管 ZGC 具有启发式功能,可以自动选择此数字,但有时,根据我们的应用程序,指定并发 GC 线程数可能会很有趣。此选项确定 GC 将占用多少 CPU,因此您必须小心要提供的容量。

将未使用的内存返回到操作系统

区别于其他的 GC 算法,ZGC 取消提交未使用的内存,将其返回给操作系统。对于可能会占用内存的应用程序,这可能是必需的。如果要禁用此选项,则可以使用 -XX:-ZUncommit

-XX:+UseZGC -Xmx<size> -XX:-ZUncommit

在 Linux 上启用大页面

此选项可提高性能,而且没有任何隐患。唯一的问题是它需要 root 授权,这就是为什么它不是默认选项,并且可能无法为您的应用程序启用它的原因。查看文档以正确设置此选项。它需要准备一些东西,选项如下所示:

-XX:+UseZGC -Xms16G -Xmx16G -XX:+UseLargePages

在 Linux 上启用透明的大页面

不建议将 Huges 页面用于对延迟敏感的应用程序,尽管它可以替代以前的调整选项。

-XX:+UseZGC -… -XX:+UseLargePages -XX:+UseTransparentHugePages

在这种情况下,我强烈建议您在应用程序中进行试验,并注意峰值,如果出现峰值,那么您可能就无法选择这种情况。

启用 NUMA 支持

ZGC 在默认情况下启用 NUMA 支持。这会将 Java 堆分配定向到 NUMA 本地内存。JVM 可以自动禁用它,如果您需要显式覆盖行为,则可以使用选项 -XX:+ UseNUMA或-XX:-UseNUMA。

-XX:+UseZGC -Xmx<size> -XX:+UseNUMA

或者

-XX:+UseZGC -Xmx<size> -XX:-UseNUMA

该算法的 Wiki 页面上还有更多详细的信息。