原文地址: 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 页面上还有更多详细的信息。