Java JVM 垃圾回收器(三):现代垃圾回收器 之 ZGC

经典垃圾回收器和现代垃圾回收器

CMS 及以后的垃圾回收器被称为"现代垃圾回收器",CMS 之前的通常称为"传统垃圾回收器"或"经典垃圾回收器"。


为什么这样区分?

1. 并发与低停顿
  • 传统垃圾回收器(如 Serial GC、Parallel GC)在回收时会长时间 Stop-The-World(STW),所有应用线程暂停,影响响应速度。
  • 现代垃圾回收器 (从 CMS 开始,如 CMS、G1、ZGC、Shenandoah)引入了并发标记、并发清理等机制,GC 线程和应用线程可以同时工作,大大降低了停顿时间,提升了应用的响应性。
2. 适应多核和大堆
  • 现代 GC 更好地支持多核 CPU 和大内存堆,能在大规模服务端场景下保持良好性能。
3. 更智能的算法
  • 现代 GC 引入了三色标记法、SATB、分区回收、预测停顿等更智能的算法和机制,能更好地平衡吞吐量与延迟。

总结

  • CMS 之前:Serial GC、Parallel GC,称为"传统"或"经典"垃圾回收器,特点是全停顿、简单高效但不适合低延迟场景。
  • CMS 及以后:CMS、G1、ZGC、Shenandoah,称为"现代垃圾回收器",特点是并发、低停顿、适合大堆和多核环境。

本质区别

现代垃圾回收器的目标是降低应用停顿时间,提高响应性和可扩展性,而传统垃圾回收器更关注实现简单和吞吐量。

ZGC(Z Garbage Collector)

一、ZGC 简介

ZGC (Z Garbage Collector)是 Oracle/OpenJDK 推出的可扩展、低延迟 的垃圾回收器,目标是在任意堆大小下(数百 MB 到数 TB) ,将 GC 停顿时间控制在10 毫秒以内。ZGC 适合对延迟极其敏感、堆内存极大的应用场景。

  • JDK11 引入实验性支持,JDK15 起正式可用。
  • 启用参数:-XX:+UseZGC

二、ZGC 的运行机制与原理

1. 堆结构

  • ZGC 将堆划分为多个等大小的 Region,每个 Region 至少 2MB。
  • Region 类型有:小对象区(Small)、大对象区(Medium/Large)、Remap 区等。

2. 并发回收,极短停顿

  • ZGC 的所有 GC 阶段几乎都是并发的,只有极少数阶段会 Stop-The-World(通常低于 1ms)。
  • 应用线程和 GC 线程几乎全程并发运行。

3. 三色标记法与染色指针(Colored Pointers)

  • ZGC 采用三色标记法,并通过**染色指针(Colored Pointers)**技术,在对象引用的高位嵌入元数据(如标记状态、转发状态)。
  • 这样可以在不暂停应用线程的情况下,安全地移动和标记对象。

4. GC 主要阶段

ZGC 的一次回收分为以下阶段:

1)Mark Start(初始标记,STW)
  • 极短暂停,标记 GC Roots 直接可达对象。
2)Concurrent Mark(并发标记)
  • 与应用线程并发,遍历对象图,标记所有可达对象。
3)Relocate Start(准备重定位,STW)
  • 极短暂停,准备对象搬迁。
4)Concurrent Relocate(并发重定位)
  • 与应用线程并发,将存活对象搬迁到新 Region,更新引用。
5)Concurrent Remap(并发修正)
  • 修正应用线程在搬迁期间产生的引用变更。
6)Reset(重置)
  • 重置内部数据结构,为下一次 GC 做准备。

5. 读屏障(Load Barrier)

  • ZGC 采用读屏障(Load Barrier) ,每次应用线程读取对象引用时,都会检查引用状态(是否已搬迁、是否需要修正)。
  • 这样即使对象在 GC 过程中被移动,应用线程也能安全访问。

6. 并发处理引用和 Finalizer

  • ZGC 对软/弱/虚引用、Finalizer 的处理也都是并发完成,进一步降低停顿。

三、ZGC 的优势

  • 极低停顿:GC 停顿时间通常低于 1ms,几乎与堆大小无关。
  • 超大堆支持:可支持 TB 级堆内存。
  • 全并发:标记、搬迁、清理等几乎全并发。
  • 无碎片:对象搬迁时整理内存,避免碎片。
  • 无需复杂调优:参数简单,自动适应大多数场景。

四、ZGC 的局限

  • 需要 64 位操作系统和 CPU 支持指针染色。
  • JDK11 及以上版本才支持。
  • 目前不支持 32 位 JVM 和部分老旧平台。

五、ZGC 启动参数示例

ruby 复制代码
-XX:+UseZGC
-XX:MaxGCPauseMillis=10
-XX:+UnlockExperimentalVMOptions   # JDK11/12 需要

六、总结

  • ZGC 是目前 Java 世界中延迟最低、可扩展性最强的垃圾回收器之一。
  • 通过 Region 划分、染色指针、读屏障和全并发设计,实现了极低的 GC 停顿和超大堆支持。
  • 适合对延迟极敏感、内存极大的服务端、金融、在线交易等场景。

染色指针和读屏障

一、染色指针(Colored Pointers)

1. 基本概念

  • 染色指针是指在对象引用(指针)的高位嵌入额外的元数据(称为"颜色"),用于记录对象的GC状态。
  • 这些"颜色"不是颜色本身,而是用几位二进制位来标记对象的不同状态,比如"已标记"、"已转发"、"需要修正"等。

2. 染色指针的作用

  • 允许GC在不暂停应用线程的情况下,安全地移动对象和更新引用。
  • 通过指针的高位,GC和应用线程可以快速判断对象的当前状态。

3. 染色指针的实现

  • 64位操作系统下,虚拟地址空间远大于实际物理内存,指针的高位通常未被使用。

  • ZGC等GC利用这些未用的高位,嵌入元数据(如2~4位),实现"染色"。

  • 例如:

    • 00:普通引用
    • 01:已标记
    • 10:已转发(对象已搬迁)
    • 11:需要修正

4. 染色指针的优势

  • 不需要额外的对象头或全局表,节省内存和提升效率。
  • 支持并发GC和对象移动时的引用透明性。

二、读屏障(Load Barrier)

1. 基本概念

  • 读屏障是一种在读取对象引用时自动执行的检查或修正逻辑
  • 在 ZGC 这样的并发移动式 GC 中,应用线程每次读取对象引用时,都会通过读屏障判断引用的状态。

2. 读屏障的作用

  • 保证应用线程在GC过程中,始终能访问到对象的最新、正确位置
  • 如果对象已被搬迁,读屏障会自动将引用修正为新地址。
  • 如果对象需要标记或其他处理,读屏障也会自动完成。

3. 读屏障的实现方式

  • 读屏障通常由JVM自动插入到每次对象引用的读取操作中(JIT编译器支持)。

  • 读屏障会检查指针的染色位,根据不同状态执行不同逻辑:

    • 如果是普通引用,直接返回。
    • 如果是已转发,自动修正引用为新地址。
    • 如果需要标记,自动完成标记。

4. 读屏障的优势

  • 允许GC和应用线程几乎全程并发,极大降低STW停顿。
  • 保证对象移动和引用修正的透明性和安全性。

三、二者协同工作示意

  1. GC 线程在并发标记/搬迁对象时,更新对象引用的染色位。
  2. 应用线程读取对象引用时,读屏障检查染色位,自动修正引用或完成标记。
  3. 整个过程无需长时间暂停应用线程,实现极低延迟的垃圾回收。

四、总结

  • 染色指针:在指针高位嵌入元数据,记录对象GC状态。
  • 读屏障:每次读取引用时自动检查和修正,保证对象移动和GC的并发安全。
  • 这两项技术是 ZGC、Shenandoah 等现代低延迟GC实现"几乎无停顿"回收的核心。

日志分析、调优建议

一、ZGC 日志样例与分析

ZGC 的 GC 日志格式与其他 GC 不同,更加结构化和易读。常见日志片段如下:

scss 复制代码
[2024-06-16T10:00:00.123+0800][info][gc,start     ] GC(0) Pause Init Mark (G1 Humongous Allocation)
[2024-06-16T10:00:00.124+0800][info][gc           ] GC(0) Pause Init Mark 0.123ms
[2024-06-16T10:00:00.124+0800][info][gc,start     ] GC(0) Concurrent Mark
[2024-06-16T10:00:00.130+0800][info][gc           ] GC(0) Concurrent Mark 6.123ms
[2024-06-16T10:00:00.130+0800][info][gc,start     ] GC(0) Pause Relocate Start
[2024-06-16T10:00:00.131+0800][info][gc           ] GC(0) Pause Relocate Start 0.234ms
[2024-06-16T10:00:00.131+0800][info][gc,start     ] GC(0) Concurrent Relocate
[2024-06-16T10:00:00.140+0800][info][gc           ] GC(0) Concurrent Relocate 9.123ms
[2024-06-16T10:00:00.140+0800][info][gc           ] GC(0) Garbage Collection (0) Complete

日志阶段说明

  • Pause Init Mark:初始标记,STW,极短暂停(通常小于1ms)。
  • Concurrent Mark:并发标记,应用线程和GC线程并发,耗时最长。
  • Pause Relocate Start:准备对象搬迁,STW,极短暂停。
  • Concurrent Relocate:并发搬迁对象,应用线程和GC线程并发。
  • Complete:本次GC完成。
  • GC(0) :这是 JVM 启动后发生的第 0 次 GC。每发生一次 GC,这个数字会递增(如 GC(1)GC(2) 等),用于区分和追踪每一次垃圾回收的全过程。

重点关注

  • STW阶段耗时(Pause Init Mark、Pause Relocate Start):应极短,通常小于1ms。
  • Concurrent阶段耗时:与应用线程并发,耗时长但不影响响应。
  • GC频率:过于频繁说明内存压力大或参数需调整。
  • 是否有"Allocation Stall" :表示分配速度过快,GC来不及回收,需关注。

二、ZGC 调优建议

1. 设置最大停顿时间

ini 复制代码
-XX:MaxGCPauseMillis=10

ZGC 会尽量将单次GC停顿控制在10ms以内(默认1ms~10ms)。

2. 合理设置堆大小

  • 堆越大,GC越高效,但内存占用也高。
  • ZGC 支持超大堆(TB级),但建议根据实际业务负载设置。

3. 关注 Humongous Allocation

  • 如果日志频繁出现 Humongous Allocation,说明有大量大对象分配,建议优化代码,减少大对象直接分配。

4. 监控 Allocation Stall

  • 如果日志出现 Allocation Stall,说明分配速度过快,GC来不及回收,建议加大堆或优化对象生命周期。

5. 开启详细日志

ruby 复制代码
-XX:+UnlockExperimentalVMOptions
-XX:+UseZGC
-XX:MaxGCPauseMillis=10
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xlog:gc*:file=gc.log:time,uptime,level,tags

6. 观察并发阶段耗时

  • 并发阶段耗时长一般不是问题,但如果GC频率高,说明内存压力大。

7. 关注 Full GC

  • ZGC 极少 Full GC,若出现需重点关注,通常是内存不足或大对象过多。

三、总结

  • ZGC 日志重点关注 STW 停顿时间、GC频率、Humongous Allocation 和 Allocation Stall。
  • 调优主要围绕堆大小、最大停顿时间和大对象分配优化。
  • ZGC 一般无需复杂调优,参数简单,适合对延迟极敏感的场景。

元空间的回收

ZGC 极少发生 Full GC ,而元空间(Metaspace)的回收确实是在 Full GC 阶段进行的

这意味着:

  • 如果 ZGC 很少或几乎不发生 Full GC,那么元空间的垃圾(比如卸载的类、ClassLoader 相关的元数据)也会很少被回收。
  • 这可能导致元空间占用持续增长,直到触发一次 Full GC,才会释放这些元空间的垃圾。

实际影响:

  • 对于类动态加载/卸载频繁的应用(如某些容器、插件系统),如果 ZGC 很少 Full GC,元空间可能会积累较多垃圾。
  • 但大多数普通应用,元空间增长不是主要瓶颈。

调优建议:

  • 可以通过监控元空间使用情况(如 JMX、jstat、GC 日志)来判断是否有泄漏或积压。
  • 如确实需要回收元空间,可以手动触发 Full GC(如 System.gc()),但一般不推荐频繁这样做。

总结:

ZGC 极少 Full GC,元空间的垃圾回收也会变得极少。如果你的应用对元空间回收有特殊需求,需要特别关注和监控。

ZGC 为什么没有清除阶段

1. ZGC 的对象回收机制

  • ZGC 在**并发搬迁(Concurrent Relocate)**阶段,会把存活对象从旧 Region 搬迁到新 Region。
  • 未被搬迁的对象(即垃圾)所在的 Region,在搬迁完成后会被整体回收和复用。
  • 也就是说,ZGC 通过 Region 的整体复用来实现垃圾的清理,而不是像 CMS 那样遍历并清除每个垃圾对象。

2. 为什么没有单独的清除阶段?

  • ZGC 的 Region 设计,使得只要 Region 里的对象都被搬迁走,这个 Region 就可以直接复用,无需单独清扫。
  • 这样可以避免传统 Sweep 阶段的 STW 或并发遍历,提升并发性和效率。

3. 总结

ZGC 没有传统意义上的"清除(Sweep)"阶段,是因为它通过并发搬迁和 Region 复用来实现垃圾回收。

这也是 ZGC 能实现极低停顿和高并发的核心原因之一。

在 ZGC(以及 G1)中,当一个 Region 里的存活对象都被搬迁到其他 Region 后,这个 Region 就会被标记为"空 Region"

下次需要分配内存时,JVM 可以直接把新的对象分配到这些空 Region,无需清理原有内容,直接覆盖即可

这种机制大大提升了回收效率,避免了传统 GC 需要遍历和清理每个垃圾对象的低效操作。

ZGC 和 G1 对比

下面详细对比 ZGCG1,并解释为什么 JDK 目前默认还是 G1。


一、ZGC 与 G1 的详细对比

特性 G1 GC ZGC
JDK版本 JDK7u4 引入,JDK9 起默认 JDK11 引入(实验),JDK15 起正式可用
目标 可预测低停顿,适合大堆和服务端 极低延迟(<10ms),适合超大堆和极致低延迟
堆结构 Region 划分,逻辑新生代/老年代 Region 划分,无固定新生代/老年代
回收算法 标记-整理(老年代),复制(新生代) 标记-整理+并发搬迁,全阶段并发
STW 停顿 停顿可控(几十到几百 ms),与堆大小相关 停顿极短(<1~10ms),与堆大小几乎无关
并发能力 标记/清理并发,回收/整理需 STW 几乎全并发,只有极短初始/搬迁 STW
大对象处理 Humongous Region,仍有碎片风险 大对象专用 Region,碎片极少
元空间回收 Full GC 时回收 Full GC 时回收
调优难度 参数较多,调优较灵活 参数极少,几乎无需调优
成熟度 非常成熟,广泛应用 新一代,JDK11+,部分特性还在完善
平台兼容性 仅支持 64 位,支持主流 64 位平台 仅支持 64 位,部分平台/容器不支持
默认 GC JDK9+ 默认 需手动指定 -XX:+UseZGC

二、ZGC 的优势

  • 极低延迟:GC 停顿时间几乎恒定在 1~10ms,无论堆多大。
  • 超大堆支持:TB 级堆无压力。
  • 全并发:GC 各阶段几乎全并发,应用线程几乎不受影响。
  • 调优简单:参数极少,开箱即用。

三、G1 的优势

  • 成熟稳定:已成为 JDK 默认 GC,兼容性和稳定性极高。
  • 可预测停顿 :可通过 -XX:MaxGCPauseMillis 设定目标停顿,适合大多数场景。
  • 广泛兼容:支持所有主流 64 位平台和容器环境。
  • 调优灵活:参数丰富,适合多种业务需求。

四、为什么 JDK 默认还是 G1?

  1. 成熟度和兼容性

    • G1 已经在生产环境广泛验证多年,兼容性极高,适合绝大多数 Java 应用。
    • ZGC 虽然优秀,但 JDK11 才引入,部分特性和平台支持还在完善,生态和工具链还不如 G1 成熟。
  2. 适用范围

    • G1 适合大多数服务端、Web、微服务等场景,延迟和吞吐平衡好。
    • ZGC 主要针对极致低延迟和超大堆场景(如金融、在线交易、实时分析等),普通应用用不上它的全部优势。
  3. 默认策略保守

    • JDK 默认 GC 需兼顾稳定性、兼容性和广泛适用性,G1 是当前最合适的选择。
    • ZGC 作为新技术,逐步推广和完善,未来有可能成为默认,但目前还不是。
  4. 平台与功能限制

    • ZGC 只支持 64 位,部分平台/容器/老旧系统不支持。
    • G1 支持更广泛的运行环境。

五、总结

  • ZGC 更先进,适合极致低延迟和超大堆,但目前还不如 G1 成熟和通用。
  • G1 兼容性、稳定性、适用范围更广,是 JDK 默认 GC 的最佳选择。
  • 未来趋势:随着 ZGC、Shenandoah 等新一代 GC 的成熟,JDK 默认 GC 可能会发生变化,但目前 G1 依然是主流。
相关推荐
皮皮林5511 小时前
SpringBoot 加载外部 Jar,实现功能按需扩展!
java·spring boot
rocksun1 小时前
认识Embabel:一个使用Java构建AI Agent的框架
java·人工智能
Java中文社群2 小时前
AI实战:一键生成数字人视频!
java·人工智能·后端
王中阳Go3 小时前
从超市收银到航空调度:贪心算法如何破解生活中的最优决策谜题?
java·后端·算法
shepherd1113 小时前
谈谈TransmittableThreadLocal实现原理和在日志收集记录系统上下文实战应用
java·后端·开源
维基框架3 小时前
Spring Boot 项目整合Spring Security 进行身份验证
java·架构
日月星辰Ace4 小时前
Java JVM 垃圾回收器(四):现代垃圾回收器 之 Shenandoah GC
java·jvm
天天摸鱼的java工程师5 小时前
商品详情页 QPS 达 10 万,如何设计缓存架构降低数据库压力?
java·后端·面试
天天摸鱼的java工程师5 小时前
设计一个分布式 ID 生成器,要求全局唯一、趋势递增、支持每秒 10 万次生成,如何实现?
java·后端·面试
阿杆5 小时前
一个看似普通的定时任务,如何优雅地毁掉整台服务器
java·后端·代码规范