Java JVM 垃圾回收器(四):现代垃圾回收器 之 Shenandoah GC

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

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 回收分为以下阶段:

  1. 初始标记(Pause Init Mark,STW)
    短暂停顿,标记 GC Roots 直接可达对象。
  2. 并发标记(Concurrent Mark)
    与应用线程并发,遍历对象图,标记所有可达对象。
  3. 最终标记(Pause Final Mark,STW)
    再次短暂停顿,修正并发标记期间发生的引用变化,确保标记准确。
  4. 并发搬迁(Concurrent Evacuation)
    存活对象被并发搬迁到新 Region,垃圾对象所在 Region 被回收复用。应用线程和 GC 线程同时参与对象搬迁。
  5. 并发清理(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:极少出现,若有需重点关注。

四、调优建议

  1. 设置最大停顿时间
ini 复制代码
// Shenandoah 会尽量将单次GC停顿控制在10ms以内。
-XX:MaxGCPauseMillis=10
  1. 合理设置堆大小

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

    • Shenandoah 极少 Full GC,若出现需重点关注,通常是内存不足或大对象过多。
  3. 开启详细日志

ruby 复制代码
-XX:+UseShenandoahGC
-XX:MaxGCPauseMillis=10
-Xlog:gc*:file=gc.log:time,uptime,level,tags
  1. 监控并发阶段耗时

    • 并发阶段耗时长一般不是问题,但如果GC频率高,说明内存压力大。
  2. 避免频繁大对象分配

    • 大对象频繁分配会导致 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

虽然 ZGCShenandoah 在原理上非常相似(都是并发标记、并发搬迁、读屏障、极低停顿),但它们的存在有以下原因和区别:


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 GCG1 GCZGC,涵盖原理、运行机制、性能特性、适用场景、局限等方面。


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 仍是默认和最广泛应用的选择。

相关推荐
皮皮林5513 小时前
SpringBoot 加载外部 Jar,实现功能按需扩展!
java·spring boot
rocksun3 小时前
认识Embabel:一个使用Java构建AI Agent的框架
java·人工智能
Java中文社群5 小时前
AI实战:一键生成数字人视频!
java·人工智能·后端
王中阳Go5 小时前
从超市收银到航空调度:贪心算法如何破解生活中的最优决策谜题?
java·后端·算法
shepherd1115 小时前
谈谈TransmittableThreadLocal实现原理和在日志收集记录系统上下文实战应用
java·后端·开源
维基框架5 小时前
Spring Boot 项目整合Spring Security 进行身份验证
java·架构
天天摸鱼的java工程师7 小时前
商品详情页 QPS 达 10 万,如何设计缓存架构降低数据库压力?
java·后端·面试
天天摸鱼的java工程师7 小时前
设计一个分布式 ID 生成器,要求全局唯一、趋势递增、支持每秒 10 万次生成,如何实现?
java·后端·面试
阿杆7 小时前
一个看似普通的定时任务,如何优雅地毁掉整台服务器
java·后端·代码规范