🎯 本文核心价值
✅ 全面解析G1、ZGC、Shenandoah三大GC回收器的底层原理与回收流程
✅ 实战参数配置,可直接用于项目调优
✅ 核心对比表格,一图看清三者差异
✅ 澄清关键认知:高版本JDK并未放弃G1,而是优化选型逻辑
📦 一、三大核心GC回收器详细解析
G1、ZGC、Shenandoah是Java不同版本的核心GC回收器,三者定位不同、设计理念各异,分别适配不同的业务场景:
表格
| 回收器 | 诞生版本 | 核心定位 |
|---|---|---|
| 🟦 G1 | JDK7引入,JDK8默认 | 通用型,兼顾吞吐量与延迟 |
| 🟩 ZGC | JDK11预览,JDK17正式 | 超低延迟,TB级大内存 |
| 🟪 Shenandoah | JDK12预览,JDK17正式 | 低延迟,与G1兼容性强 |
🟦 1.1 G1垃圾回收器(Garbage-First)------ 通用型兼顾吞吐量与延迟
G1是Oracle官方推出的区域分代式垃圾回收器 ,于JDK7中首次引入,JDK8u20及以后版本成为默认GC(JDK8早期版本默认Parallel GC),JDK9-JDK20持续作为默认GC,是Java历史上应用最广泛的通用型回收器,核心定位是"兼顾吞吐量与延迟",适配绝大多数常规业务场景。
📌 核心设计原理
G1的核心设计理念是 "优先回收垃圾最多的区域"(Garbage-First) ,采用 "区域分代模型" ,无需手动分代调整,自动管理年轻代与老年代,支持大内存(最大可支持数TB内存),平衡吞吐量与延迟的同时,避免CMS回收器的内存碎片问题。
区域分代模型: 将JVM堆内存划分为多个大小相等的独立区域(Region),每个Region的大小可配置(1MB~32MB,默认根据堆内存自动计算),区域类型分为三种:
表格
| 区域类型 | 存放对象 | 回收算法 | 回收频率 |
|---|---|---|---|
| 🟦 Eden Region(伊甸区) | 新创建的对象 | 复制算法 | ⭐⭐⭐ 最高 |
| 🟦 Survivor Region(幸存区) | Minor GC后存活的对象 | 复制算法 | ⭐⭐⭐ 最高 |
| 🟠 Old Region(老年代) | 存活时间较长的对象 | 标记-整理 | ⭐ 较低 |
💡 G1核心特点: 没有固定的年轻代和老年代边界,通过动态调整各区域的数量,实现年轻代与老年代的灵活管理;兼顾吞吐量与延迟,可通过参数控制最大GC停顿时间;✅ 无内存碎片,适合大内存场景。
📌 垃圾回收流程(核心三步)
G1的垃圾回收分为Minor GC (年轻代回收)、Mixed GC (混合回收)、🔴 Full GC(全局回收)三种,其中Minor GC和Mixed GC是核心,Full GC尽量避免(会导致长停顿)。
🟢 ① Minor GC(年轻代回收) ------ ⚡ 停顿短,毫秒级
- 触发条件: Eden Region满时触发
- 回收过程: 采用复制算法 ,将Eden Region和From Survivor Region中存活的对象,复制到To Survivor Region;若对象存活时间达到阈值(默认15),则晋升到Old Region;回收完成后,Eden和From Survivor清空,To Survivor与From Survivor角色互换
- 停顿特点: 只回收年轻代区域,停顿时间较短(毫秒级) ,对主线程影响较小
🟠 ② Mixed GC(混合回收) ------ ⚡ 可控停顿
- 触发条件: 老年代区域占比达到阈值(默认45% ),触发混合回收
- 回收过程: 同时回收年轻代(Eden+Survivor)和部分老年代Region(优先回收垃圾最多的Region,即"Garbage-First"),采用复制算法(年轻代)+ 标记-整理算法(老年代)
- 停顿特点: 停顿时间比Minor GC长,但比Full GC短,可通过参数控制停顿时间上限(如设置最大停顿时间为200ms)
🔴 ③ Full GC(全局回收)⚠️ 尽量避免!
- 触发条件: Mixed GC无法回收足够内存、老年代满、元空间满等极端情况
- 回收过程: 停止所有主线程(STW,Stop The World) ,对整个堆内存进行全面回收,采用标记-整理算法
- 停顿特点: 停顿时间极长(秒级)!会严重影响系统可用性,是G1的核心性能痛点
⚠️ 关键提醒: Full GC是G1最大的性能痛点,调优的核心目标就是减少Full GC的发生频率!
📌 核心参数配置(实战必配)
G1的默认参数基本适配常规场景,针对高并发、大内存项目,需手动调整参数优化性能,核心参数如下:
bash
# 1. 启用G1垃圾回收器(JDK8u20及以后默认,手动配置可兼容低版本)
-XX:+UseG1GC
# 2. 设置堆内存大小(建议设置为物理内存的1/2~2/3)
-Xms4g -Xmx4g
# 3. ⭐⭐⭐ 设置最大GC停顿时间(核心参数!根据业务延迟要求调整)
-XX:MaxGCPauseMillis=200
# 4. 设置年轻代占比(默认0.4,可根据业务调整,范围0.1~0.9)
-XX:G1NewSizePercent=40
-XX:G1MaxNewSizePercent=70
# 5. ⭐⭐ 设置老年代触发混合回收的阈值(默认45%,降低可减少Full GC)
-XX:InitiatingHeapOccupancyPercent=40
# 6. 禁用ExplicitGC(避免System.gc()触发Full GC)
-XX:+DisableExplicitGC
# 7. 设置Region大小(默认自动计算,手动设置需为2的幂,1MB~32MB)
-XX:G1HeapRegionSize=4m
🟩 1.2 ZGC垃圾回收器 ------ 低延迟大内存首选 🔥
ZGC是Oracle研发的低延迟垃圾回收器 ,首次在JDK11中以预览特性引入,JDK17中正式稳定 ,JDK21中进一步优化推出分代ZGC(Generational ZGC) ,核心定位是"超低延迟、支持超大内存",解决G1在大内存场景下的长停顿问题,适配延迟敏感型、大内存业务(如金融交易、实时数据处理)。
📌 核心设计原理
ZGC的核心设计目标是 "GC停顿时间控制在毫秒级以内"(通常<10ms) ,支持TB级堆内存,其设计理念摒弃了G1的分代模型(早期无分代,JDK21引入分代),采用"基于Region的无分代(或分代)模型",通过两大核心技术实现大部分GC操作与用户线程并发执行:
表格
| 核心技术 | 作用 | 优势 |
|---|---|---|
| 🎨 颜色指针 | 通过给对象指针添加颜色标记,记录对象的回收状态(可达、不可达、正在迁移) | 无需维护复杂的记忆集,✅ 减少内存开销和GC耗时 |
| 🔒 读屏障 | 在读取对象指针时插入一段轻量代码,实现并发迁移对象时的指针修正 | 避免用户线程访问到无效对象,✅ 无需冻结用户线程 |
💡 ZGC核心特点:
- 🟢 低延迟:STW停顿<10ms,支持TB级堆内存
- 🟢 大部分GC操作与用户线程并发执行,对业务影响极小
- 🟢 JDK21引入分代ZGC ,结合分代假设与ZGC的超低延迟,✅ 进一步提升吞吐量(年轻代采用复制算法,老年代采用并发转移)
- 🟢 无内存碎片,内存利用率高
📌 垃圾回收流程(核心四步,几乎无长停顿)
ZGC的回收流程全程以 "并发" 为核心,仅在2个阶段有极短的STW停顿(微秒级或毫秒级),其余阶段均与用户线程并发执行,彻底解决G1的长停顿痛点。
表格
| 步骤 | 阶段名称 | 类型 | 说明 |
|---|---|---|---|
| ① | 初始标记 | 🔴 STW | 标记与GC Roots直接关联的对象,停顿时间极短(微秒级),与堆内存大小无关,仅与GC Roots数量相关 |
| ② | 并发标记 | 🟢 并发 | 遍历对象图,标记出所有可达对象,与用户线程并发执行,耗时取决于堆中存活对象数量 |
| ③ | 并发预备重分配 | 🟢 并发 | 筛选出需要回收的Region(垃圾占比高的Region),准备进行对象迁移,与用户线程并发执行 |
| ④ | 并发重分配与并发重映射 | 🟢 并发 | 将回收Region中的存活对象迁移到新的Region,同时通过读屏障修正所有指向旧对象的指针,✅ 全程与用户线程并发执行,无STW停顿 |
🎯 对比G1: G1的回收阶段(复制/整理)需要STW,而ZGC的整个回收过程几乎都是并发执行的,这是ZGC实现超低延迟的根本原因。
📌 核心参数配置(实战必配)
ZGC仅支持JDK11及以上版本,核心参数配置简洁,重点关注堆内存、并发线程数等,适配低延迟场景:
bash
# 1. 启用ZGC垃圾回收器(JDK11+预览,JDK17+正式稳定)
-XX:+UseZGC
# 2. 设置堆内存大小(支持TB级,根据业务需求调整)
-Xms16g -Xmx16g
# 3. 设置ZGC并发线程数(默认是CPU核心数的1/8,可根据CPU性能调整)
-XX:ZGCThreads=8
# 4. ⭐⭐⭐ 启用分代ZGC(JDK21+支持,提升吞吐量!)
-XX:+ZGenerational
# 5. 设置ZGC内存释放延迟(默认300秒,可调整为立即释放)
-XX:ZUncommitDelay=0
🟪 1.3 Shenandoah垃圾回收器 ------ 低延迟兼容多场景
Shenandoah是由RedHat 独立研发的低延迟垃圾回收器,2014年RedHat将其贡献给OpenJDK,首次在JDK12中以预览特性引入,JDK17中正式稳定(⚠️ Oracle JDK中未集成,仅OpenJDK支持) ,核心定位与ZGC一致------"低延迟、支持大内存",但设计细节与ZGC存在差异,更注重兼容性和灵活性。
📌 核心设计原理
Shenandoah与G1有诸多相似之处,二者都使用基于Region的堆内存布局,且有着用于存放大对象的Humongous Region,默认的回收策略也同样是优先处理回收价值最大的Region,甚至在初始标记、并发标记等阶段的思路高度一致,共享了一部分代码。但Shenandoah相比G1有三大核心改进,实现了更低的延迟:
表格
| 核心技术 | 作用 | 与G1的区别 |
|---|---|---|
| 🔄 并发整理算法 | GC线程垃圾收集的过程中可以和用户线程并发执行 | G1的回收阶段可以多线程并行,但🔴 无法与用户线程并发 |
| 🔗 连接矩阵 | 用二维表格记录跨Region的引用关系,✅ 替代记忆集 | G1耗费大量内存和计算资源去维护记忆集 |
| 👉 Brooks指针 | 通过转发指针记录对象的新地址,确保用户线程能正确访问正在迁移的对象 | G1迁移对象时🔴 需要STW |
💡 通俗理解连接矩阵: 连接矩阵可简单理解为一张二维表格,若Region N有对象指向Region M,就在表格的N行M列中打上标记,回收时通过这张表格即可得知哪些Region之间存在跨代引用。
📌 垃圾回收流程(核心八步,3次短STW)
Shenandoah的回收流程分为8个阶段,其中仅3个阶段有短暂的STW停顿,其余阶段均与用户线程并发执行,停顿时间极短,且与堆内存大小无关,仅与GC Roots数量相关。
表格
| 步骤 | 阶段名称 | 类型 | 说明 |
|---|---|---|---|
| ① | 初始标记 | 🔴 STW | 与G1、ZGC一致,标记与GC Roots直接关联的对象,停顿时间极短 |
| ② | 并发标记 | 🟢 并发 | 遍历对象图,标记出全部可达的对象,与用户线程并发执行,时间长短取决于堆中存活对象的数量及对象图结构复杂度 |
| ③ | 最终标记 | 🔴 STW | 处理剩余的SATB扫描,统计出回收价值最高的Region,构成回收集,停顿时间短暂 |
| ④ | 并发清理 | 🟢 并发 | 清理整个区域内无存活对象的Immediate Garbage Region,与用户线程并发执行 |
| ⑤ | 🟪 并发回收(核心差异!) | 🟢 并发 | 将回收集中的存活对象复制到其他未使用的Region,通过Brooks指针解决并发迁移与访问的冲突,与用户线程并发执行 |
| ⑥ | 初始引用更新 | 🔴 STW | 建立线程集合点,确保所有并发回收线程完成对象移动任务,停顿时间极短 |
| ⑦ | 并发引用更新 | 🟢 并发 | 线性搜索内存中的引用类型,将指向旧对象的引用修正为新地址,与用户线程并发执行 |
| ⑧ | 最终引用更新 | 🔴 STW | 修正GC Roots中的引用,是Shenandoah的最后一次停顿,停顿时间仅与GC Roots数量相关 |
📌 核心参数配置(实战必配)
Shenandoah仅支持JDK12及以上版本(OpenJDK),核心参数配置如下:
bash
# 1. 启用Shenandoah垃圾回收器(JDK12+预览,JDK17+正式稳定,仅OpenJDK支持)
-XX:+UseShenandoahGC
# 2. 设置堆内存大小(支持TB级,根据业务需求调整)
-Xms16g -Xmx16g
# 3. 设置Shenandoah并发线程数(默认CPU核心数的1/4,可调整)
-XX:ShenandoahGCThreads=8
# 4. ⭐⭐ 设置Shenandoah回收模式(默认iu,并发回收;可设为iu、satb等)
-XX:ShenandoahGCHeuristics=iu
# 5. 禁用Shenandoah分代(默认禁用,JDK17+支持分代,可启用)
-XX:-ShenandoahGenerational
📊 二、G1、ZGC、Shenandoah核心对比(实战选型关键)
三者的核心差异集中在延迟、内存支持、并发能力、兼容性等方面,以下从核心维度进行全面对比,清晰区分各自的适用场景,为项目选型提供依据:
表格
| 对比维度 | 🟦 G1垃圾回收器 | 🟩 ZGC垃圾回收器 | 🟪 Shenandoah垃圾回收器 |
|---|---|---|---|
| 核心定位 | 通用型,兼顾吞吐量与延迟 | 低延迟,超大内存支持 | 低延迟,兼容性强(与G1兼容) |
| 引入版本 | JDK7(预览),JDK8u20+默认 | JDK11(预览),JDK17+正式稳定 | JDK12(预览),JDK17+正式稳定 |
| STW停顿时间 | 毫秒级(Minor GC)~ 🔴 秒级(Full GC) ,大内存场景停顿明显 | 🟢 毫秒级(<10ms),几乎无长停顿,与堆大小无关 | 🟢 毫秒级(<10ms),3次短STW,与堆大小无关 |
| 内存支持 | 最大数TB(理论),实际常用16GB~64GB | 最大数TB,✅ 支持TB级超大内存 | 最大数TB,✅ 支持TB级超大内存 |
| 并发能力 | 部分阶段并发(如并发标记),回收阶段并行,🔴 无法与用户线程并发 | ✅ 全程几乎并发,仅2个短STW阶段,并发能力最强 | ✅ 全程几乎并发,仅3个短STW阶段,并发能力强 |
| 核心技术 | 区域分代模型、复制+标记-整理算法 | 🎨 颜色指针、🔒 读屏障、无分代(JDK21+分代) | 🔄 并发整理、🔗 连接矩阵、👉 Brooks指针、无分代(可选分代) |
| 内存碎片 | ✅ 无内存碎片(标记-整理算法) | ✅ 无内存碎片(并发重分配) | ✅ 无内存碎片(并发整理) |
| 兼容性 | ✅✅✅ 所有JDK8+版本支持,生态最完善,适配所有常规场景 | ✅ JDK11+支持,Oracle JDK/OpenJDK均支持,生态成熟 | ⚠️ JDK12+支持,仅OpenJDK支持,Oracle JDK需额外集成 |
| 调优复杂度 | 🟡 中等,需调整停顿时间、Region大小等参数 | 🟢 低,参数简洁,无需复杂调优 | 🟡 中等,需调整并发线程数、回收模式等 |
| 适用场景 | 常规业务、单体/微服务、兼容性优先、无极致延迟要求 | 低延迟业务、金融交易、实时数据、TB级大内存场景 | 低延迟业务、OpenJDK环境、与G1兼容的迁移项目 |
📊 补充说明:
🔹 性能优先级: 低延迟场景下,🟩 ZGC ≈ 🟪 Shenandoah > 🟦 G1;吞吐量场景下,🟦 G1 ≈ 🟩 ZGC(JDK21+分代)> 🟪 Shenandoah
🔹 迁移成本: 从🟦 G1迁移到🟪 Shenandoah ✅ 成本最低(二者设计相似),迁移到🟩 ZGC成本稍高(需适配颜色指针、读屏障)
🔹 版本支持: JDK8仅支持G1,JDK11支持G1+ZGC(预览),🟢 JDK17+支持G1+ZGC(正式)+Shenandoah(正式,OpenJDK)
🔍 三、关键澄清:高版本JDK并未放弃G1,而是优化选型逻辑
很多开发者存在一个认知误区:❌ "高版本JDK(JDK17、JDK21)放弃了G1,转而使用ZGC/Shenandoah"
✅ 事实并非如此! 高版本JDK并未完全放弃G1,而是调整了GC的默认选型逻辑------将G1作为"通用兜底选项",ZGC/Shenandoah作为"低延迟、大内存场景的优先选项",核心原因如下:
📌 3.1 高版本JDK对G1的态度:保留+持续优化
从版本演进来看,G1在高版本中始终被保留,且持续优化:
- 🟦 JDK9-JDK20: G1✅ 始终是默认GC回收器,Oracle持续优化其STW停顿时间、混合回收效率,解决G1的性能痛点
- 🟦 JDK21: 默认GC调整为ZGC(若硬件支持,否则fallback到G1),但G1仍被完整保留,针对常规场景进行了进一步优化
- 核心原因: G1的 "通用性" 是ZGC/Shenandoah无法替代的,其兼顾吞吐量与延迟的特性,适配绝大多数不需要极致低延迟的常规业务,且生态最完善、兼容性最强,无需开发者进行复杂适配
📌 3.2 高版本优先推荐ZGC/Shenandoah的核心原因(并非放弃G1)
高版本JDK将ZGC/Shenandoah作为低延迟场景的优先选项,核心是为了适配云计算、高并发、大内存的现代业务需求,弥补G1的不足,具体原因如下:
❶ G1的性能瓶颈无法彻底解决: G1的混合回收、Full GC仍会产生长停顿(秒级),在TB级大内存、低延迟场景(如金融交易、实时流处理)中,无法满足业务需求;而ZGC/Shenandoah的STW停顿控制在10ms以内,✅ 彻底解决长停顿痛点,这是高版本优先推荐二者的核心原因
❷ 现代业务对低延迟、大内存的需求提升: 随着云计算、微服务、实时数据处理的普及,越来越多的业务(如支付、直播、物联网)对延迟敏感,且需要TB级大内存支持(如大数据平台),ZGC/Shenandoah的设计理念完全适配这些场景,而G1在这类场景下的性能表现不足
❸ ZGC/Shenandoah的成熟度提升: JDK17之后,ZGC和Shenandoah已成为正式稳定特性,生态逐渐完善,兼容性不断提升,能够满足企业级生产环境的需求;而JDK21对ZGC的优化(分代ZGC),进一步弥补了其吞吐量不足的问题,使其既能满足低延迟,又能兼顾吞吐量,竞争力大幅提升
❹ 选型逻辑的优化: 高版本JDK的GC选型逻辑更贴合业务场景------常规业务(无极致延迟要求)仍可使用G1,低延迟、大内存业务优先使用ZGC/Shenandoah,而非"一刀切"放弃G1,实现了✅ "通用场景兜底、特殊场景优化" 的平衡
📌 3.3 高版本JDK中G1的定位(仍有不可替代的价值)
即使ZGC/Shenandoah已成熟,G1在高版本中仍有不可替代的价值,主要适配以下场景:
表格
| 场景 | 为什么选G1 |
|---|---|
| 🏢 常规业务场景 | 无需极致低延迟,追求吞吐量与延迟的平衡,如传统单体应用、普通微服务 |
| 🔗 兼容性优先场景 | 项目依赖的框架、中间件对ZGC/Shenandoah适配不足,✅ G1的生态最完善,适配所有Java框架 |
| 💾 低内存场景 | 堆内存较小(<16GB),ZGC/Shenandoah的低延迟优势不明显,✅ G1的性能更优、资源消耗更低 |
| 🔄 迁移过渡场景 | 从JDK8升级到高版本的项目,继续使用G1 ✅ 可降低迁移成本,无需修改大量配置 |
❓ 四、常见问题解答(GC选型高频)
Q1:JDK8中能否使用ZGC/Shenandoah?
❌ 不能。 ZGC首次出现在JDK11(预览),Shenandoah首次出现在JDK12,JDK8的GC体系中没有这两种回收器,强行配置相关参数会报错;JDK8仅支持G1、Parallel、Serial、CMS四种回收器。
Q2:JDK17/JDK21中,G1、ZGC、Shenandoah该如何选型?
✅ 优先根据业务场景选型:
- ① 🟩 低延迟、大内存(>16GB)、延迟敏感(如金融、实时):优先选🟩 ZGC(Oracle JDK)或🟪 Shenandoah(OpenJDK)
- ② 🟦 常规业务、兼容性优先、无极致延迟要求:选🟦 G1
- ③ 💾 低内存(<16GB) 、追求简单配置:选🟦 G1
Q3:高版本JDK放弃G1了吗?为什么?
❌ 没有放弃。 高版本JDK只是将ZGC/Shenandoah作为低延迟、大内存场景的优先选项,G1仍被保留并持续优化;核心原因是G1的通用性强、生态完善,适配绝大多数常规业务,而ZGC/Shenandoah主要解决G1在低延迟、大内存场景下的性能瓶颈,✅ 二者互补而非替代。
Q4:ZGC和Shenandoah哪个更好?
二者定位一致,各有优势:
表格
| 对比项 | 🟩 ZGC | 🟪 Shenandoah |
|---|---|---|
| JDK支持 | ✅ Oracle JDK原生支持 | ⚠️ 仅OpenJDK支持 |
| 生态成熟度 | ✅ ✅ 更成熟 | 一般 |
| 迁移成本 | 稍高(需适配颜色指针/读屏障) | ✅ ✅ 最低(与G1设计相似) |
| 适合场景 | Oracle JDK环境、极致低延迟场景 | OpenJDK环境、从G1迁移的项目 |
Q5:G1的Full GC频繁该如何优化?
🛠️ 优化步骤:
① 优先调整 InitiatingHeapOccupancyPercent 参数,降低老年代触发混合回收的阈值(如 🔴 45% → 🟢 40%),让Mixed GC提前触发,减少老年代堆积
② 检查是否有大对象频繁创建、🔴 对象内存泄漏等问题
③ 适当✅ 增大堆内存,避免内存不足
④ 若仍无法解决,可考虑✅ 升级JDK版本,使用ZGC/Shenandoah
📝 五、总结
表格
| GC回收器 | 核心定位 | 关键优势 | 最佳场景 |
|---|---|---|---|
| 🟦 G1 | 通用兜底 | 兼顾吞吐与延迟,✅ 生态最完善 | 常规业务、兼容性优先、低内存 |
| 🟩 ZGC | 低延迟首选 | 停顿 ✅ <10ms,Oracle JDK支持 | 金融交易、实时数据、TB级大内存 |
| 🟪 Shenandoah | 低延迟兼容 | ✅ G1迁移成本最低 | OpenJDK环境、G1迁移项目 |
🎯 关键认知: 高版本JDK(JDK17、JDK21)并未放弃G1 ,而是优化了选型逻辑------🟦 G1适配常规业务,🟩 ZGC/🟪 Shenandoah适配低延迟、大内存业务,不是替代,而是互补。
开发者在选型时,无需盲目追新,应结合 JDK版本 × 业务延迟要求 × 堆内存大小 × 兼容性需求,选择最适合的GC回收器。