丝般顺滑!全新垃圾回收器 ZGC 原理与调优|龙蜥技术

简介: 本系列共计 3 篇文章,上篇介绍 GC 的基本概念和 ZGC 的规模化实践(点击正文相关阅读即可),本篇将介绍 ZGC 的原理和调优,第三篇将介绍 Dragonwell 对于 ZGC 的生产就绪改造。

yjtp (1).png我们在上一篇文章中介绍了 ZGC 的基本概念和阿里的 ZGC 规模化实践,看到阿里业务和云上客户享受到 ZGC 带来的响应时间优化,同时也遭遇到了一些实际问题。为了更好地使用 ZGC,我们需要了解一些 ZGC 的原理,并学会分析 ZGC 日志,对 ZGC 进行调优。

相关阅读《上篇|丝般顺滑!全新垃圾回收器 ZGC 初体验》

9758F457-6F07-4076-AE86-9797DAE702DD.png

ZGC原理

从宏观的角度看,ZGC 是一种并发( concurrent )的压缩式( compacting ) GC 算法:

  • 并发:在 Java 线程运行的同时,GC 线程在后台默默执行;
  • 压缩式:定期将堆中活跃对象整理到一起,解决内存碎片化问题。


相比于 Java 原有的百毫秒级的暂停的 Parallel GC 和 G1,以及未解决碎片化问题的 CMS ,并发和压缩式的 ZGC 可谓是 Java GC 能力的一次重大飞跃—— GC 线程在整理内存的同时,可以让 Java 线程继续执行。 


ZGC 采用标记-压缩策略来回收 Java 堆:ZGC 首先会并发标记( concurrent mark )堆中的活跃对象,然后并发转移( concurrent relocate )将部分区域的活跃对象整理到一起。这里与早先的 Java GC 不同之处在于,目前 ZGC 是单代垃圾回收器,在标记阶段会遍历堆中的全部对象。


那么问题来了,ZGC 是如何做到并发标记和转移呢?这就要提到ZGC背后的核心技术——读屏障( load barrier )和染色指针( colored pointer )。


ZGC 的读屏障是在指针加载的操作的时候,插入一段针对该指针的处理逻辑:

  • 如果指针指向已经被转移的对象,那么读屏障将修正该指针;
  • 在标记阶段,如果该指针未被标记,那么读屏障将标记该指针;
  • 在转移阶段,如果该指针指向需要转移的区域,那么该指针指向的对象将被转移,然后修正该指针。


读屏障能够确保在 GC 线程与 Java 线程并发运行的情况下,每次指针载入都能访问到正确的对象。


ZGC 的染色指针是将指针高位未使用的 bit 作为指针的颜色,表示指针的状态,使得读屏障在处理指针的时候,能够直接得到该指针的状态,决定采用何种方式来处理指针。生产就绪的 ZGC 支持 2^44=16TB 的寻址空间,实际上使用了 44+4=48 个 bit 作为染色指针的地址,其中高 4 位是指针的颜色。染色指针与读屏障相互配合,将读屏障中的条件判断部分转换为对于指针颜色的判断,如果指针颜色是“错误”的,那么读屏障就会将指针修复为“正确”的。640.png

ZGC日志分析

单次ZGC周期实际执行过程中需要三次短促的暂停,每次暂停之后是若干并发阶段。


[2020-12-23T13:30:57.402+0800] GC(10) Garbage Collection (Allocation Rate)
[2020-12-23T13:30:57.408+0800] GC(10) Pause Mark Start 2.918ms
[2020-12-23T13:30:58.083+0800] GC(10) Concurrent Mark 674.216ms
[2020-12-23T13:30:58.087+0800] GC(10) Pause Mark End 1.336ms
[2020-12-23T13:30:58.105+0800] GC(10) Concurrent Process Non-Strong References 18.293ms
[2020-12-23T13:30:58.111+0800] GC(10) Concurrent Reset Relocation Set 5.533ms
[2020-12-23T13:30:58.111+0800] GC(10) Concurrent Destroy Detached Pages 0.001ms
[2020-12-23T13:30:58.121+0800] GC(10) Concurrent Select Relocation Set 10.148ms
[2020-12-23T13:30:58.130+0800] GC(10) Concurrent Prepare Relocation Set 9.083ms
[2020-12-23T13:30:58.136+0800] GC(10) Pause Relocate Start 2.452ms
[2020-12-23T13:30:58.203+0800] GC(10) Concurrent Relocate 66.595ms
... (此处忽略一些数据统计信息)
[2020-12-23T13:30:58.203+0800] GC(10) Garbage Collection (Allocation Rate) 62020M(76%)->41270M(50%)

上面的 GC 日志展示了一个典型的 ZGC 周期,每一行每个周期中以 Pause 开头的阶段即为暂停阶段,这三个暂停阶段分别为

  • Pause Mark Start(标记开始暂停);
  • Pause Mark End(标记结束暂停);
  • Pause Relocate Start(转移开始暂停)。


可以看到上面的 GC 日志中,ZGC 三个暂停阶段的时间明显低于 10ms 。这三个暂停阶段主要承担 GC Roots 的标记和转移,以及标记线程同步的工作。


这三个暂停阶段后面以 Concurrent 开头的阶段即为并发阶段,其中最核心的两个阶段是

  • Concurrent Mark(并发标记);
  • Concurrent Relocate(并发转移)。

其余的并发阶段主要是并发转移之前的一些预备工作。

640 (1).png

ZGC 各个阶段的图示


目前 ZGC 的并发标记会标记整个堆中的所有活跃对象,有别于 G1/CMS/Parallel GC 等分代 GC ,属于单代 GC 。并发标记的过程会顺便修复堆中的错误指针。为了降低转移对象的负担,ZGC 的并发转移的策略会选择碎片化程度达到某个阈值(ZFragmentationLimit)的区域,类似于 G1 的 Garbage First 策略。

ZGC调优

下面介绍ZGC相关的调优细节,用户应至少完成基本调优部分。


基本调优

一般来说,ZGC 应当设置堆空间大小( Xmx )和并发 GC 线程数量(ConcGCThreads)。我们建议所有 ZGC 用户应当开启 GC 日志,通常建议开启- Xlog:gc*:gc.log:time ,能够记录较多的 ZGC 细节。

堆空间大小

GC 通常需要开发者指定堆空间大小,具体数值应该大于堆内活跃对象的总大小。冗余空间比例越高,GC 性能通常越好。例如估计对象总大小达到 32GB ,可设置- Xmx40g ,代表开启 40GB 的堆。


ZGC 与传统 GC 的不同之处在于,ZGC 在回收对象的同时,Java 线程也在分配新对象。因此 ZGC 比传统 GC 需要更高比例的冗余空间。


每一轮 ZGC 的过程中分配的对象总大小可以用“分配速度·单轮 ZGC 时间”来估算,因此堆空间的大小应大于“活跃对象的总大小+单次 ZGC 期间分配的对象总大小”。


上述的“分配速度”和“单轮 ZGC 时间”均可以在 GC 日志中找到统计信息。


并发GC线程数量

ZGC 缺省的并发 GC 线程数量是 1/8 的 CPU 核数,例如 16 核的机器,如果没有指定 ConcGCThreads,那么 ZGC 就会开启 2 个并发 GC 线程。


在 GC 日志中,如果频繁出现“ Allocation Stall ”,代表回收跟不上分配的情况,那么可能需要提高 ConcGCThreads 了。当然 ConcGCThreads 不能无限制地增加,因为过多并发的 GC 线程会占据 CPU 资源,甚至影响 Java 线程的正常执行。


注意并发 GC 线程(ConcGCThreads)与并行 GC 线程(ParallelGCThreads)是不同的,前者与 Java 线程可以并发执行,后者是 GC 暂停时的 GC 线程。


进阶调优

Product ready ZGC 的功能同时也支持若干进阶 ZGC 调优选项,可参考 Alibaba Dragonwell 11.0.11.7 的使用说明

https://github.com/alibaba/dragonwell11/wiki/Alibaba-Dragonwell-11-Release-Notes#110117


进阶调优最核心的部分是 GC 触发时机的控制。由于 ZGC 在回收时依然分配对象,于是 ZGC 不能等到堆空间满了以后才触发 GC ,而需要提前一段时间触发 GC ,使得 ZGC 执行的过程中堆空间不会变满,导致 Allocation Stall 或者 OOM。但是如果 ZGC 触发地过于频繁,则 CPU 资源消耗变多,从而降低吞吐率。


Dragonwell11 支持以下 GC 触发时机相关选项:

  • ZAllocationSpikeTolerance:ZGC 通过估算“分配速度·单轮 ZGC 时间”来估算单次 ZGC 期间分配的对象总大小,只要这个总大小小于当前剩余的堆空间,就需要触发 GC 。但是由于 Java 业务的分配速度往往不是稳定的,因此需要为分配速度乘上“毛刺系数” ZAllocationSpikeTolerance ,从而保守地提前触发 GC 。如果 Java 业务分配速度不稳定,偶尔有 Allocation Stall 的发生,那么就应当考虑适当增加 ZAllocationSpikeTolerance。
  • ZCollectionInterval:定时触发 GC,避免 GC 间隔过长。
  • ZProactive:字面意思是“主动触发 GC ”,用于处理分配速率较低的情况。
  • ZHighUsagePercent:堆的水位超过此百分比,则触发 ZGC 。


只要满足以上 GC 触发时机的条件之一,ZGC 就会触发。


SoftMaxHeapSize 选项可以设置 ZGC 堆空间的“软上限”,介于 Xmx 和 Xms 之间。以上的 ZAllocationSpikeTolerance/ZProactive/ZHighUsagePercent 均以 SoftMaxHeapSize 的值作为 ZGC 堆空间的“软上限”,当分配速度过快时可以扩展到至多 Xmx 的堆空间,当分配速度较慢时可以将堆空间收缩到 Xms 。SoftMaxHeapSize 通常需要打开 - XX:+ZUncommit 。


除此之外还有一些有用的进阶调优功能:

  • ZFragmentationLimit:控制 ZGC 的对象的碎片化程度,ZFragmentationLimit 越低,ZGC 回收越彻底;
  • ZMarkStackSpaceLimit:调节 ZGC 标记栈空间大小;
  • ZUnloadClassesFrequency:控制 ZGC 类卸载的频率;
  • ZRelocationReservePercent:控制 ZGC 的预留分配空间,降低 OOM 风险;
  • ZStatisticsInterval:控制 ZGC 日志中统计信息的输出频率,原先 10 秒一次输出可能会影响 GC 详细信息的解读。


(上面的 ZHighUsagePercent/ZUnloadClassesFrequency/ZRelocationReservePercent 是 Dragonwell11 的特有选项。若切换到其他版本的 OpenJDK 时,请避免使用这些选项。)

在后面的篇章中,读者将看到我们的 Alibaba Dragonwell11 通过对 ZGC 进行生产就绪改造,从而解决生产实践中的一些问题。


关于作者

唐浩,2019 年加入阿里云编程语言与编译器团队,目前从事 JVM 内存管理优化方向的工作。


现 DragonWell 已加入 龙蜥社区 (OpenAnolis )Java 语言与虚拟机 SIG,同时龙蜥操作系统(Anolis OS )8 版本支持 DragonWell 云原生 Java ,欢迎大家加入社区 SIG,参与社区共建。

SIG 地址:https://openanolis.cn/sig/java/doc/216166872482840581

sig截图.png

——完——


加入龙蜥社群

加入微信群:添加社区助理-龙蜥社区小龙(微信:openanolis_assis),备注【龙蜥】拉你入群;加入钉钉群:扫描下方钉钉群二维码。欢迎开发者/用户加入龙蜥OpenAnolis社区交流,共同推进龙蜥社区的发展,一起打造一个活跃的、健康的开源操作系统生态!

dra.png            1.jpeg

Dragonwell钉钉交流群              龙蜥社区钉钉交流群


关于龙蜥社区

龙蜥社区(OpenAnolis)是由企事业单位、高等院校、科研单位、非营利性组织、个人等按照自愿、平等、开源、协作的基础上组成的非盈利性开源社区。龙蜥社区成立于2020年9月,旨在构建一个开源、中立、开放的Linux上游发行版社区及创新平台。


短期目标是开发龙蜥操作系统(Anolis OS)作为CentOS替代版,重新构建一个兼容国际Linux主流厂商发行版。中长期目标是探索打造一个面向未来的操作系统,建立统一的开源操作系统生态,孵化创新开源项目,繁荣开源生态。


龙蜥OS 8.4已发布,支持x86_64和ARM64架构,完善适配Intel、飞腾、海光、兆芯、鲲鹏芯片。欢迎下载:https://openanolis.cn/download


加入我们,一起打造面向未来的开源操作系统!

Https://openanolis.cn

相关文章
|
Prometheus 监控 算法
CMS圣经:CMS垃圾回收器的原理、调优,多标+漏标+浮动垃圾 分析与 研究
本文介绍了CMS(Concurrent Mark-Sweep)垃圾回收器的工作原理、优缺点及常见问题,并通过具体案例分析了其优化策略。重点探讨了CMS的各个阶段,包括标记、并发清理和重标记
CMS圣经:CMS垃圾回收器的原理、调优,多标+漏标+浮动垃圾 分析与 研究
|
存储 算法 Java
G1原理—5.G1垃圾回收过程之Mixed GC
本文介绍了G1的Mixed GC垃圾回收过程,包括并发标记算法详解、三色标记法如何解决错标漏标问题、SATB如何解决错标漏标问题、Mixed GC的过程、选择CollectSet的算法
G1原理—5.G1垃圾回收过程之Mixed GC
|
存储 监控 架构师
ZGC圣经:ZGC垃圾回收器的原理、调优,ZGC 漏标的 分析与 研究
ZGC圣经:ZGC垃圾回收器的原理、调优,ZGC 漏标的 分析与 研究
|
存储 缓存 算法
G1原理—3.G1是如何提升垃圾回收效率
本文深入探讨了G1垃圾回收器提升GC效率的核心机制,包括记忆集(RSet)、位图(BitMap)和卡表(CardTable)的设计与作用。记忆集通过记录跨代引用避免了不必要的老年代遍历,位图用于高效描述内存使用状态以优化标记过程,而卡表则在节约记忆集内存的同时提供更详细的引用信息。此外,文章还解析了DCQ(Dirty Card Queue)和DCQS(Dirty Card Queue Set)机制如何异步更新RSet,确保在高并发场景下的性能与准确性。这些设计共同提升了G1在标记、清理及整理内存时的效率。
966 10
|
存储 算法 Java
G1原理—6.G1垃圾回收过程之Full GC
本文详细探讨了G1垃圾回收器对Full GC(FGC)的优化处理,涵盖FGC的前置处理、整体流程及并行化改进。重点分析了传统FGC串行化的局限性以及G1通过Region分区和RSet机制实现并行标记的优势,包括任务窃取提升效率、跨分区压缩以生成空闲Region等技术细节。此外,文章还介绍了G1的新特性——字符串去重优化,通过判断char数组一致性减少重复字符串占用内存,从而提升内存使用效率。总结部分全面回顾了G1在FGC中的各项优化措施及其带来的性能改善。
G1原理—6.G1垃圾回收过程之Full GC
|
存储 算法 Java
G1原理—4.G1垃圾回收的过程之Young GC
本文详细解析了G1垃圾回收器中YGC(Young Generation Collection)的完整流程,包括并行与串行处理阶段。内容涵盖YGC相关参数设置、YGC与Mixed GC及FGC的关系、新生代垃圾回收的具体步骤(如标记存活对象、复制到Survivor区、动态调整Region数量等),以及并行阶段的多线程操作和串行阶段的关键任务(如处理软引用、整理卡表、重构RSet)。
G1原理—4.G1垃圾回收的过程之Young GC
|
缓存 算法 Java
JVM深入原理(八)(一):垃圾回收
弱引用-作用:JVM中使用WeakReference对象来实现软引用,一般在ThreadLocal中,当进行垃圾回收时,被弱引用对象引用的对象就直接被回收.软引用-作用:JVM中使用SoftReference对象来实现软引用,一般在缓存中使用,当程序内存不足时,被引用的对象就会被回收.强引用-作用:可达性算法描述的根对象引用普通对象的引用,指的就是强引用,只要有这层关系存在,被引用的对象就会不被垃圾回收。引用计数法-缺点:如果两个对象循环引用,而又没有其他的对象来引用它们,这样就造成垃圾堆积。
392 0
|
算法 Java 对象存储
JVM深入原理(八)(二):垃圾回收
Java垃圾回收过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部分阶段需要停止所有的用户线程。这个过程被称之为StopTheWorld简称STW,如果STW时间过长则会影响用户的使用。一般来说,堆内存越大,最大STW就越长,想减少最大STW,就会减少吞吐量,不同的GC算法适用于不同的场景。分代回收算法将整个堆中的区域划分为新生代和老年代。--超过新生代大小的大对象会直接晋升到老年代。
394 0
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
1452 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS

热门文章

最新文章