经典垃圾回收器和现代垃圾回收器
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,称为"现代垃圾回收器",特点是并发、低停顿、适合大堆和多核环境。
本质区别 :
现代垃圾回收器的目标是降低应用停顿时间,提高响应性和可扩展性,而传统垃圾回收器更关注实现简单和吞吐量。
Shenandoah GC
一、简介
Shenandoah GC 是 Red Hat 主导开发的 OpenJDK 低延迟垃圾回收器,目标是在任意堆大小下将 GC 停顿时间降到最低(通常在 1~10ms 级别),非常适合对响应时间极为敏感的 Java 应用。JDK12 起为正式特性,JDK17 LTS 推荐使用。
二、运行机制与原理
1. 堆结构
- 堆被划分为多个等大小的 Region(区域),每个 Region 可动态作为新生代或老年代使用。
- 这种设计便于并发回收和内存整理,类似 G1、ZGC。
2. 回收流程
Shenandoah 的一次 GC 回收分为以下阶段:
- 初始标记(Pause Init Mark,STW)
短暂停顿,标记 GC Roots 直接可达对象。 - 并发标记(Concurrent Mark)
与应用线程并发,遍历对象图,标记所有可达对象。 - 最终标记(Pause Final Mark,STW)
再次短暂停顿,修正并发标记期间发生的引用变化,确保标记准确。 - 并发搬迁(Concurrent Evacuation)
存活对象被并发搬迁到新 Region,垃圾对象所在 Region 被回收复用。应用线程和 GC 线程同时参与对象搬迁。 - 并发清理(Concurrent Cleanup)
并发释放无用 Region,回收内存。
3. 读屏障(Load Barrier)
- Shenandoah 采用读屏障技术:每次应用线程读取对象引用时,自动检查和修正引用地址。
- 如果对象已被搬迁,读屏障会自动将引用修正为新地址,保证应用线程始终访问到最新对象。
4. 极短的 STW 阶段
- 只有初始标记、最终标记等极少数阶段会有 STW,且时间极短(通常低于 10ms)。
三、日志分析
日志样例
scss
[2024-06-16T10:00:00.123+0800][info][gc,start ] GC(0) Pause Init Mark
[2024-06-16T10:00:00.124+0800][info][gc ] GC(0) Pause Init Mark 0.5ms
[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.0ms
[2024-06-16T10:00:00.130+0800][info][gc,start ] GC(0) Concurrent Evacuation
[2024-06-16T10:00:00.140+0800][info][gc ] GC(0) Concurrent Evacuation 8.0ms
[2024-06-16T10:00:00.140+0800][info][gc ] GC(0) Pause Final Mark 0.7ms
[2024-06-16T10:00:00.141+0800][info][gc ] GC(0) Concurrent Cleanup 1.0ms
日志阶段说明
- Pause Init Mark:初始标记,STW,极短暂停。
- Concurrent Mark:并发标记,应用线程和GC线程并发,耗时最长。
- Concurrent Evacuation:并发搬迁,存活对象被搬迁到新 Region。
- Pause Final Mark:最终标记,STW,修正并发期间的引用变化。
- Concurrent Cleanup:并发清理,释放无用 Region。
重点关注
- STW阶段耗时(Pause Init Mark、Pause Final Mark):应极短,通常低于10ms。
- Concurrent阶段耗时:与应用线程并发,耗时长但不影响响应。
- GC频率:过于频繁说明内存压力大或参数需调整。
- Full GC:极少出现,若有需重点关注。
四、调优建议
- 设置最大停顿时间
ini
// Shenandoah 会尽量将单次GC停顿控制在10ms以内。
-XX:MaxGCPauseMillis=10
-
合理设置堆大小
- 堆越大,GC越高效,但内存占用也高。
- Shenandoah 支持超大堆(TB级),但建议根据实际业务负载设置。
-
关注 Full GC
- Shenandoah 极少 Full GC,若出现需重点关注,通常是内存不足或大对象过多。
-
开启详细日志
ruby
-XX:+UseShenandoahGC
-XX:MaxGCPauseMillis=10
-Xlog:gc*:file=gc.log:time,uptime,level,tags
-
监控并发阶段耗时
- 并发阶段耗时长一般不是问题,但如果GC频率高,说明内存压力大。
-
避免频繁大对象分配
- 大对象频繁分配会导致 Region 回收压力增大,建议优化代码,减少大对象直接分配。
五、局限
- 仅支持 64 位 JVM:不支持 32 位平台。
- JDK 版本要求:JDK12 及以上才支持,JDK17 LTS 推荐。
- 部分平台兼容性:早期版本在 Windows 支持有限,JDK17+ 已较完善。
- CPU 占用略高:大量并发操作可能导致 CPU 使用率高于传统 GC。
- 元空间回收依赖 Full GC:类卸载和元空间回收仍需 Full GC,极少发生但需关注。
- 调优参数较少:虽然易用,但在极端场景下灵活性不如 G1。
六、启动参数
ruby
-XX:+UseShenandoahGC # 启用 Shenandoah GC
-XX:MaxGCPauseMillis=10 # 期望最大 GC 停顿时间(毫秒)
-XX:+UnlockExperimentalVMOptions # JDK12/13 可能需要
-Xlog:gc*:file=gc.log:time,uptime,level,tags # 打印详细 GC 日志
常用调优参数:
-XX:ShenandoahGCHeuristics=aggressive
更积极地回收,适合低延迟场景。-XX:ShenandoahUncommitDelay=60000
控制 Region 释放回操作系统的延迟(毫秒)。
七、适用场景
- 对延迟极敏感的 Java 应用(如金融、在线交易、实时分析等)。
- 堆内存极大、对吞吐和响应时间都有要求的服务端应用。
八、总结
Shenandoah GC 通过并发标记、并发搬迁和读屏障技术,实现了极低停顿和高吞吐,适合现代对延迟要求极高的 Java 应用场景。
日志分析重点关注 STW 停顿时间、GC频率、Full GC 及大对象分配。
调优主要围绕堆大小、最大停顿时间和大对象分配优化。
局限包括平台支持、元空间回收等,启动参数简单易用。
ZGC 和 Shenandoah
Shenandoah 比 ZGC 更早出现。
- Shenandoah 最早在 2014 年由 Red Hat 启动开发,2018 年合并进 OpenJDK。
- ZGC 由 Oracle 开发,2017 年左右开始公开,2018 年合并进 OpenJDK。
所以,Shenandoah 的开发和开源时间都早于 ZGC。
虽然 ZGC 和 Shenandoah 在原理上非常相似(都是并发标记、并发搬迁、读屏障、极低停顿),但它们的存在有以下原因和区别:
1. 开发背景和主导方不同
- ZGC 由 Oracle 主导开发,集成在 Oracle JDK 和 OpenJDK。
- Shenandoah 由 Red Hat 主导开发,最初主要服务于 Red Hat Enterprise Linux 及其生态。
2. 平台和兼容性
- Shenandoah 早期对 Linux、Windows、macOS、AArch64 等平台的支持更积极,尤其在 OpenJDK 社区和 Red Hat 发行版中优先集成。
- ZGC 最初只支持 Linux/x86_64,后续才逐步支持更多平台。
3. 实现细节和调优策略
- 两者虽然都用读屏障,但实现细节、屏障插入方式、堆管理、并发阶段的调度等有差异。
- Shenandoah 提供了不同的启发式策略(如
aggressive
),调优参数和行为略有不同。
4. 社区和生态需求
- 不同的企业和社区有不同的需求和优先级,Red Hat 需要一个能快速集成到自家产品和 Linux 发行版的低延迟 GC。
- Oracle 推动 ZGC 作为 OpenJDK 的主流低延迟 GC 方案。
5. 开源多样性和创新
- JVM 社区鼓励多种 GC 并存,促进创新和技术进步。
- 用户可以根据实际业务、平台、兼容性和调优需求选择最合适的 GC。
总结:
ZGC 和 Shenandoah 虽然原理类似,但由于主导方、平台支持、实现细节、社区需求等不同,二者并存可以满足更广泛的用户和场景需求,也推动了 JVM 垃圾回收技术的持续创新和进步。
G1 ZGC Shenandoah 对比
详细深入对比 Shenandoah GC 、G1 GC 和 ZGC,涵盖原理、运行机制、性能特性、适用场景、局限等方面。
Shenandoah GC、G1 GC、ZGC 对比
特性/GC | G1 GC | Shenandoah GC | ZGC |
---|---|---|---|
JDK版本 | JDK7u4+,JDK9起默认 | JDK12+(JDK17 LTS 推荐) | JDK11+(JDK15起正式) |
目标 | 可预测低停顿,适合大堆和服务端 | 极低延迟(1~10ms),堆大小无关 | 极低延迟(<10ms),堆大小无关 |
堆结构 | Region 划分,逻辑新生代/老年代 | Region 划分,无固定新生代/老年代 | Region 划分,无固定新生代/老年代 |
回收算法 | 标记-整理(老年代),复制(新生代) | 并发标记-并发搬迁(Mark-Compact) | 并发标记-并发搬迁(Mark-Relocate) |
STW 停顿 | 可控(几十到几百 ms),与堆大小相关 | 极短(1~10ms),与堆大小无关 | 极短(<10ms),与堆大小无关 |
并发能力 | 标记/清理并发,回收/整理需STW | 标记、搬迁、清理等几乎全并发 | 标记、搬迁、清理等几乎全并发 |
屏障类型 | 写屏障(Write Barrier) | 读屏障(Load Barrier) | 读屏障(Load Barrier) |
大对象处理 | Humongous Region,仍有碎片风险 | Region 级别搬迁,碎片极少 | Humongous Region,碎片极少 |
内存碎片 | 有可能,需 Full GC 整理 | 几乎无碎片 | 几乎无碎片 |
元空间回收 | Full GC 时回收 | Full GC 时回收 | Full GC 时回收 |
调优难度 | 参数较多,调优灵活 | 参数较少,几乎无需调优 | 参数极少,几乎无需调优 |
成熟度 | 非常成熟,广泛应用 | 新一代,JDK17+ 推荐,Red Hat 主导 | 新一代,Oracle/OpenJDK 主导 |
平台兼容性 | 支持主流 64 位平台(不支持 32 位) | 仅支持 64 位,JDK17+ 跨平台完善 | 仅支持 64 位,部分平台/容器不支持 |
默认 GC | JDK9+ 默认 | 需手动指定 | 需手动指定 |
1. 运行机制与原理
G1 GC
- 堆划分为多个 Region,新生代和老年代是逻辑概念。
- 回收分 Young、Mixed、Full GC,优先回收垃圾最多的 Region。
- 标记-整理算法,但回收和整理阶段仍有较长 STW。
- 写屏障用于并发标记期间的引用变更跟踪。
Shenandoah GC
- 堆划分为 Region,无固定新生代/老年代。
- 全流程并发:标记、搬迁、清理等几乎全并发,只有极短 STW。
- 读屏障:每次读取对象引用时自动修正,保证对象搬迁安全。
- 并发搬迁:存活对象被并发搬迁到新 Region,垃圾 Region 直接复用。
ZGC
- 堆划分为 Region,无固定新生代/老年代。
- 全流程并发,STW 阶段极短(通常<1ms)。
- 染色指针+读屏障:对象引用高位嵌入元数据,读屏障自动修正引用。
- 并发搬迁,Region 级别回收,极低碎片。
2. 性能特性
- G1:停顿可控,适合大多数服务端应用,但大堆下停顿仍可能较长。
- Shenandoah:极低停顿,堆大小对停顿影响极小,适合极致低延迟场景。
- ZGC:极低停顿,TB 级堆无压力,适合超大堆和极致低延迟场景。
3. 适用场景
- G1:大多数服务端、Web、微服务等,延迟和吞吐平衡好,兼容性强。
- Shenandoah:对延迟极敏感、堆极大、吞吐和响应都要求高的场景(如金融、在线交易、实时分析)。
- ZGC:对延迟极致敏感、超大堆(TB 级)、需要极短停顿的场景。
4. 局限与注意事项
- G1:大堆下停顿不可避免,参数多需调优,不支持 32 位,元空间回收依赖 Full GC。
- Shenandoah:仅 64 位,JDK12+,早期 Windows 支持有限,元空间回收依赖 Full GC。
- ZGC:仅 64 位,JDK11+,部分平台/容器不支持,元空间回收依赖 Full GC。
5. 启动参数
G1 GC(JDK9+ 默认):
ruby
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
Shenandoah GC:
ruby
-XX:+UseShenandoahGC
-XX:MaxGCPauseMillis=10
-Xlog:gc*:file=gc.log:time,uptime,level,tags
ZGC:
ruby
-XX:+UseZGC
-XX:MaxGCPauseMillis=10
-Xlog:gc*:file=gc.log:time,uptime,level,tags
6. 总结与选择建议
- G1:首选通用型 GC,兼容性好,适合绝大多数生产环境。
- Shenandoah:对延迟极致敏感、堆极大、对吞吐和响应都要求高时优先考虑。
- ZGC:超大堆、极致低延迟、对停顿极度敏感的场景首选。
未来趋势:随着 Shenandoah 和 ZGC 的成熟,低延迟 GC 会逐步成为主流,但目前 G1 仍是默认和最广泛应用的选择。