JDK 15中Z垃圾收集器算法 - JesúsNavarrete


随着JDK 15发布,最新版本的ZGC现在可以投入生产了。简而言之,ZGC是一个可伸缩的低延迟垃圾收集器,最大GC暂停时间为10毫秒,能够处理从几兆字节到多TB的堆,最大吞吐量降低了15%。
 
JVM垃圾收集器
到目前为止,JVM引入了一个有趣的垃圾收集器算法列表,下面列出了最重要的算法和简短的描述,让我们记住它们。

  • 串行(低内存占用):它使用单个线程来完成工作。它适用于单处理器计算机,并且针对内存不足(通常是嵌入式系统)进行了优化。
  • 并行(吞吐量收集器):它并行进行次要收集,以减少垃圾收集的开销。它是为在多处理器硬件上运行的中型到大型数据集应用程序而构建的。
  • CMS(并发标记扫描收集器):它旨在缩短垃圾收集器的暂停时间。专为具有大量长寿命对象或大量长期使用期限的应用程序而设计。CMS收集器是generational的。
  • G1(吞吐量/等待时间平衡): Garbage-First是服务器样式的垃圾收集器,设计用于具有大内存的多处理器计算机。它是一个压缩收集器(它压缩得足够避免使用没有细粒度的列表进行分配)。
  • ZGC(低延迟)

串行和并行称为“世界停止”算法。CMS在JDK 9中已弃用,用G1代替。
比较它们很有趣。ZGC可同时执行所有繁重的操作,而其他算法则无法做到(更多详细信息请参见下文)。
 
深入Z垃圾收集器
ZGC是并发的低延迟算法,除了线程堆栈扫描外,它所有并发的操作(标记,压缩,参考处理,重定位集选择,StringTable清理,JNI WeakRef清理,JNI GlobalRefs扫描和类卸载)。这使得该算法对于低延迟确实非常有用。
值得一提的是,当前,暂停时间并不随堆大小而增加,但是,暂停时间却随根集root-set大小(您的应用程序正在使用的Java线程数)而增加。
从算法的角度来看,它是一个并发收集器,它在Java线程继续执行的同时完成了所有繁重的工作。这是一个基于区域的收集器,这意味着将堆划分为较小的区域,并且压缩工作将集中在这些区域的子集上,即垃圾最多的区域。它是NUMA感知的,由于CPU有本地内存,因此可以减少延迟。它使用彩色指针和负载屏障,将在以下各节中详细介绍。它是一个单一的一代收集器,它没有以前回收机制的年轻或年老代。
 
ZGC阶段
ZGC的GC周期分为三个暂停。
在第一阶段(暂停标记开始)中,ZGC遍历对象图以将对象标记为活动或无用。此阶段还包括实时数据的重新映射。
第二阶段是“暂停标记结束”,在此阶段完成参考预处理。在该阶段还完成了类卸载和重定位集的选择。
暂停重定位启动是最后一个阶段,在此阶段要进行大量的压缩堆工作。
  • 彩色的指针

它们是ZGC中的核心设计概念。该算法使用64位对象指针中的一些未使用的位来存储一些元数据,从而可以查找,标记,定位和重新映射对象。点击标题见配图
  • 负载屏障

它是JIT在某些重要位置注入的代码。目的是检查加载的对象引用是否具有不良颜色。当线程从堆中加载对象引用时,将运行负载屏障代码。
 
微调选项
如果要使用ZGC算法,JDK15之前版本必须解锁实验选项:
-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,因此您必须小心要提供的容量。
-XX:+UseZGC -Xmx<size> -XX:ConcGCThreads=<number>
  • 将未使用的内存返回到操作系统

与其他GC算法不同,ZGC取消提交未使用的内存,将其返回给操作系统。对于可能会占用内存的应用程序,这可能是必需的。如果要禁用此选项,则可以使用-XX:-ZUncommit。
-XX:+UseZGC -Xmx<size> -XX:-ZUncommit
  • 在Linux上启用大页面

此选项可提高性能,并且没有缺点或副作用。唯一的问题是它需要root特权,这就是为什么它不是默认选项,并且可能无法为您的应用程序启用它的原因。查看文档以正确设置此选项。它需要准备一些东西,选项如下所示:
-XX:+UseZGC -Xms16G -Xmx16G -XX:+UseLargePages
  • 在Linux上启用透明的大页面

不建议将这种大页面用于对延迟敏感的应用程序,尽管它可以替代以前的调整选项。
-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

 
总结
ZGC团队的路线图吸引了我两件事。
首先是团队正在努力将最大暂停时间减少到1 ms。线程堆栈扫描将同时进行,这也意味着暂停时间不会随根集大小的增加而增加(JEP 376)。
其次,它们将使ZGC成为世代相传的产品,因为大多数对象都是短命的,这将是另一个不错的改进。