丝般顺滑!全新垃圾回收器 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

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
相关文章
|
3月前
|
存储 算法 Oracle
极致八股文之JVM垃圾回收器G1&ZGC详解
本文作者分享了一些垃圾回收器的执行过程,希望给大家参考。
|
13天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
35 6
|
1月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
1月前
|
存储 监控 算法
G1 垃圾回收器:底层原理与调优过程
【10月更文挑战第9天】G1(Garbage-First)垃圾回收器是Java虚拟机(JVM)中一款面向服务端应用的垃圾收集器,它在JDK 1.7中引入,并从JDK 9开始成为默认的垃圾回收器。G1的设计目标是在有限的停顿时间内,尽可能地提高系统的吞吐量,特别是在多CPU和大内存的场景下表现出色。
60 3
|
1月前
|
算法 Java 开发者
Java中的垃圾回收机制:从原理到实践
Java的垃圾回收机制(Garbage Collection, GC)是其语言设计中的一大亮点,它为开发者提供了自动内存管理的功能,大大减少了内存泄漏和指针错误等问题。本文将深入探讨Java GC的工作原理、不同垃圾收集器的种类及它们各自的优缺点,并结合实际案例展示如何调优Java应用的垃圾回收性能,旨在帮助读者更好地理解和有效利用Java的这一特性。
|
1月前
|
算法 JavaScript 前端开发
垃圾回收算法的原理
【10月更文挑战第13天】垃圾回收算法的原理
24 0
|
2月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
2月前
|
监控 算法 Java
掌握Java的垃圾回收机制:从原理到实践
在Java的世界中,垃圾回收(Garbage Collection,简称GC)是一块神秘的领域,它如同一位默默无闻的清洁工,确保内存中不再使用的对象得到妥善处理。本文将带你走进垃圾回收的大门,探索它的工作原理、常见算法及其在实际应用中的调优策略。无论你是初学者还是有一定经验的开发者,这篇文章都将为你揭开垃圾回收的神秘面纱,让你的Java程序运行得更加高效和稳定。
63 5
|
4月前
|
Java 运维
开发与运维技术问题之ava对象头压缩技术支持所有的Java垃圾回收器如何解决
开发与运维技术问题之ava对象头压缩技术支持所有的Java垃圾回收器如何解决
33 1
|
4月前
|
缓存 监控 算法
Java面试题:描述Java垃圾回收的基本原理,以及如何通过代码优化来协助垃圾回收器的工作
Java面试题:描述Java垃圾回收的基本原理,以及如何通过代码优化来协助垃圾回收器的工作
88 8

热门文章

最新文章

下一篇
无影云桌面