深入理解JVM《ZGC:超低延迟的可扩展垃圾收集器》

简介: ZGC是JDK 11引入、15正式发布的低延迟垃圾收集器,目标是堆大小无关的10ms内停顿。其核心通过“着色指针”和“读屏障”实现标记与重定位的并发执行,极大减少STW时间,适用于大内存、高实时场景,虽有CPU开销但吞吐影响小,调优简单,是未来Java GC的发展方向。

ZGC(Z Garbage Collector)是Oracle在JDK 11中引入的一款实验性的低延迟垃圾收集器,并在JDK 15中正式发布。它的设计目标是:在任意堆内存大小下(从几百MB到数TB),将垃圾收集的停顿时间控制在10毫秒以内。这是一个极其雄心勃勃的目标,旨在解决G1和CMS在大堆应用上仍然可能产生较长停顿的问题。

核心目标与设计理念

ZGC的核心设计理念是几乎所有的垃圾收集工作都是并发执行的,从而将STW(Stop-The-World)停顿缩短到仅有的几个毫秒级阶段。为了实现这一目标,ZGC采用了两大基石技术:着色指针读屏障

核心技术:着色指针与读屏障

1. 着色指针 (Colored Pointers)

这是ZGC的基石。它颠覆了传统观念,在指针本身而不是在对象头上存储元数据信息。

在64位平台上,一个指针的理论寻址空间是2^64字节,这是一个极其巨大的空间。而实际上,现代硬件和操作系统并不会使用完所有64位。Linux/x86_64平台通常使用48位虚拟地址空间(高16位为符号扩展),而AMD64架构支持52位物理地址空间。ZGC巧妙地利用了这些未使用的指针位来存储信息。

ZGC使用了64位指针中的4个位(称为“元位”)来标记对象的状态:

  • Finalizable:对象只能通过finalizer访问。
  • Remapped:对象已被重映射(即新的地址已被记录)。
  • Marked1:标记状态1。
  • Marked0:标记状态0。

这些“颜色”标记了对象在GC周期中的状态。通过操作指针的颜色位,ZGC可以高效地并发执行标记和重定位(压缩)操作,而无需暂停所有应用线程。

2. 读屏障 (Load Barrier)

读屏障是JVM在从堆内存加载引用时插入的一小段代码。可以把它想象成一个“陷阱”或一个“钩子”。每当应用程序线程从堆中读取一个对象引用时(例如 Object foo = obj.field;),ZGC的读屏障就会被触发。

读屏障的核心作用:根据着色指针的颜色位,“治愈” 这个引用。

  • 如果读到的指针颜色显示该对象尚未被处理(例如,尚未被标记或尚未被重定位),读屏障会主动介入,完成相应的处理工作(例如,标记对象或更新引用到新地址)。
  • 如果指针颜色显示一切正常,则读屏障几乎不做任何事,开销极低。

正是“着色指针+读屏障”的结合,使得ZGC的标记和重定位两大最耗时的任务能够与应用线程并发执行。

ZGC的执行过程

ZGC的GC周期可以分为几个阶段,其中标记重定位是核心。

1. 初始标记 (Pause Mark Start)

  • 任务:从GC Roots(线程栈、全局变量等)开始,标记所有直接可达的对象。这是一个非常快速的、STW的过程。
  • 过程:遍历GC Roots,将直接引用的对象压入标记栈中。

2. 并发标记 (Concurrent Mark)

  • 任务:从标记栈中的对象开始,递归遍历整个对象图,标记所有存活的对象。
  • 状态完全并发。应用线程仍在运行。
  • 关键技术:读屏障。当应用线程加载一个尚未被标记的对象的引用时,读屏障会拦截该引用,并完成对该对象的标记(将其颜色位设为Marked0或Marked1),并将其加入标记栈。这确保了所有在并发标记阶段被访问到的对象都会被正确标记。

3. 最终标记 (Pause Mark End)

  • 任务:处理一些边缘情况(如弱引用处理),并确保标记栈是空的,标志着整个对象图的标记工作已经完成。
  • 状态:一个非常短暂的 STW 停顿。

4. 并发准备重定位 (Concurrent Prepare for Relocate)

  • 任务:根据标记结果(存活对象),筛选出哪些内存区域包含最多的垃圾(即最适合进行压缩回收),并创建重分配集(Relocation Set)。

5. 初始重定位 (Pause Relocate Start)

  • 任务:为重分配集中的每个Region分配转发指针(Forwarding Pointer)所需的数据结构。
  • 状态:一个非常短暂的 STW 停顿。

6. 并发重定位 (Concurrent Relocate)

  • 任务:这是ZGC最核心、最神奇的部分。它将重分配集中的存活对象复制到新的Region中
  • 状态完全并发
  • 工作原理
  • GC线程开始复制存活对象到新的Region。
  • 同时,应用线程仍在运行,并可能访问已被重定位或尚未重定位的对象。
  • 读屏障再次发挥关键作用
  • 如果一个应用线程尝试访问一个尚未被重定位的对象,读屏障会先完成这个对象的复制工作,然后更新应用线程手中的引用,使其指向新地址。
  • 如果一个应用线程尝试访问一个已经被其他线程重定位的对象,读屏障会通过对象旧地址上的“转发指针”来“治愈”这个引用,直接返回新地址。
  • 这个过程确保了所有引用最终都会被正确地更新到新地址,而无需暂停应用线程。

下图展示了ZGC如何通过其并发的标记和重定位阶段,将漫长的STW时间分解为多个极短的停顿,从而实现其超低延迟的目标:

ZGC如何解决“对象消失”问题

与G1的SATB和CMS的增量更新不同,ZGC的并发标记基于一种称为 “指针着色” 的机制,它本身通过读屏障就足以保证标记的正确性。

在ZGC的并发标记阶段:

  1. 对象的状态通过指针的颜色位来标识(Marked0/Marked1)。
  2. 当应用线程试图将一个引用写入某个字段时(A.field = B),它只是简单地存储了一个指向B的指针(可能带着未标记的颜色)。
  3. 当另一个线程(或之后同一个线程)读取 A.field 时,读屏障会被触发
  4. 读屏障检查引用B的颜色。如果它尚未被标记,读屏障会立即将其标记(修改颜色位),然后再将“治愈”后的正确引用返回给应用线程。

这种方式确保了:

  • 任何被存活对象引用的对象,在引用被读取时都会被立刻标记,从而不会被错误回收。
  • 它不需要像G1那样维护复杂的RSet来跟踪跨Region引用,因为“治愈”工作是在读取时即时完成的。

ZGC的优缺点与调优

优点

  1. 超低停顿:停顿时间通常不超过10ms,且不随堆大小增长而增长。
  2. 高吞吐量:虽然引入了读屏障,但其设计非常高效,吞吐量损失相对于G1通常只在15%以内。
  3. 简化的调优:参数远比G1简单,核心目标通常只需设定最大停顿时间(-XX:MaxGCPauseMillis)。

缺点与注意事项

  1. CPU开销:读屏障虽然高效,但仍会带来额外的CPU开销。在CPU极度紧张的应用中,可能需要评估其影响。
  2. JDK版本:生产环境强烈建议使用其正式版后的LTS版本,如JDK 17或JDK 21,以获得最佳的稳定性和性能。
  3. 观察性工具:传统的jstat等工具对ZGC的支持有限,需要依赖ZGC自己的日志和更新的工具(如JDK的jcmd、GC日志分析)来进行监控和调优。

关键参数

  • 启用ZGC-XX:+UseZGC
  • 设置最大停顿时间目标-XX:MaxGCPauseMillis=5 (单位ms,默认无目标,但ZGC会尽力优化)
  • 启用并行GC线程动态调整-XX:+UseDynamicGCThreads (推荐,默认开启)
  • 开启GC日志-Xlog:gc*:gc.log (JDK 9+ Unified Logging格式)

总结

ZGC代表了Java垃圾收集技术的前沿。它通过革命性的着色指针读屏障技术,将GC停顿时间降低到了一个前所未有的水平,几乎让Java应用程序感觉不到垃圾收集的存在。尽管它需要更高的JDK版本支持,并且可能带来轻微的CPU开销,但对于追求极致响应速度的应用(如金融交易、大数据处理、实时游戏服务器),ZGC是一个改变游戏规则的选择。随着JDK的持续演进,ZGC正变得越来越成熟和稳定,是未来Java大内存低延迟应用的默认选择。

相关文章
|
1月前
|
Arthas 监控 数据可视化
深入理解JVM《JVM监控与性能工具实战 - 系统的诊断工具》
掌握JVM监控与诊断工具是Java性能调优的关键。本文系统介绍jps、jstat、jmap、jstack等命令行工具,以及jconsole、VisualVM、JMC、Arthas、async-profiler等可视化与高级诊断工具,涵盖GC分析、内存泄漏定位、线程死锁检测及CPU热点追踪,助力开发者全面提升线上问题排查能力。(238字)
|
存储 Oracle Java
分代 ZGC 详解
本文主要介绍JDK21中的分代ZGC详解,包括染色指针、内存屏障等核心概念及ZGC JVM参数介绍 ZGC(Z Garbage Collector)是Java平台上的一种垃圾收集器,它是由Oracle开发的,旨在解决大堆的低延迟垃圾收集问题。ZGC是一种并发的分代垃圾收集器,它主要针对具有大内存需求和低停顿时间要求的应用程序。
分代 ZGC 详解
|
存储 算法 Oracle
极致八股文之JVM垃圾回收器G1&ZGC详解
本文作者分享了一些垃圾回收器的执行过程,希望给大家参考。
|
1月前
|
监控 算法 Java
深入理解JVM《G1垃圾收集器:面向局部收集与停顿模型的里程碑》
G1收集器是JDK 9+默认的高性能垃圾回收器,采用Region分区模型,实现可预测停顿时间。它通过RSet跟踪跨区引用,结合SATB算法确保并发标记准确性,兼顾低延迟与高吞吐,适用于大内存多核场景。
|
1月前
|
Arthas 缓存 监控
深入理解JVM最后一章《常见问题排查思路与调优案例 - 综合实战》
本文系统讲解JVM性能调优的哲学与方法论,强调避免盲目调优。提出三大原则:测量优于猜测、权衡吞吐量/延迟/内存、由上至下排查问题,并结合CPU高、OOM、GC频繁等典型场景,提供标准化排查流程与实战案例,助力科学诊断与优化Java应用性能。
|
1月前
|
人工智能 API 数据安全/隐私保护
近期非常风靡非常逼真的AI视频内容由sora生成的视频是怎么回事?-优雅草卓伊凡
近期非常风靡非常逼真的AI视频内容由sora生成的视频是怎么回事?-优雅草卓伊凡
378 12
近期非常风靡非常逼真的AI视频内容由sora生成的视频是怎么回事?-优雅草卓伊凡
|
1月前
|
Java API 开发者
告别“线程泄露”:《聊聊如何优雅地关闭线程池》
本文深入讲解Java线程池优雅关闭的核心方法与最佳实践,通过shutdown()、awaitTermination()和shutdownNow()的组合使用,确保任务不丢失、线程不泄露,助力构建高可靠并发应用。
|
4月前
|
消息中间件 缓存 监控
MQ消息积压 / Rocketmq 积压 最全的处理方案。 (秒懂+图解+史上最全)
MQ消息积压 / Rocketmq 积压 最全的处理方案。 (秒懂+图解+史上最全)
MQ消息积压 / Rocketmq 积压 最全的处理方案。 (秒懂+图解+史上最全)
|
1月前
|
存储 缓存 算法
深入理解JVM《JVM内存区域详解 - 世界的基石》
Java代码从编译到执行需经javac编译为.class字节码,再由JVM加载运行。JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)区域,其中堆是GC主战场,方法区在JDK 8+演变为使用本地内存的元空间,直接内存则用于提升NIO性能,但可能引发OOM。
|
1月前
|
人工智能 监控 Java
零代码改造 + 全链路追踪!Spring AI 最新可观测性详细解读
Spring AI Alibaba 通过集成 OpenTelemetry 实现可观测性,支持框架原生和无侵入探针两种方式。原生方案依赖 Micrometer 自动埋点,适用于快速接入;无侵入探针基于 LoongSuite 商业版,无需修改代码即可采集标准 OTLP 数据,解决了原生方案扩展性差、调用链易断链等问题。未来将开源无侵入探针方案,整合至 AgentScope Studio,并进一步增强多 Agent 场景下的观测能力。
1205 28