JVM 垃圾收集器(GC)完全指南
涵盖 Serial、Parallel、CMS、G1、ZGC、Shenandoah 六大垃圾收集器
目录
- JVM 堆内存基础结构
- Serial GC(串行收集器)
- Parallel GC(并行收集器)
- CMS GC(并发标记清除)
- G1 GC(Garbage First)
- ZGC(Z Garbage Collector)
- Shenandoah GC
- GC 对比总结
- GC 调优实战
一、JVM 堆内存基础结构
在了解各 GC 之前,先理解通用的堆内存结构:
┌─────────────────────────────────────────────────────────────┐
│ JVM Heap │
├─────────────────────┬───────────────────────────────────────┤
│ Young Generation │ Old Generation (Tenured) │
│ (新生代 ~1/3) │ (老年代 ~2/3) │
├──────────┬──────────┤ │
│ Eden │ Survivor │ │
│ (~8/10) │ S0 + S1 │ │
│ │ (各1/10) │ │
└──────────┴──────────┴───────────────────────────────────────┘
对象晋升流程:
Eden → Survivor(S0↔S1) → Old Generation
分代假设
- 弱分代假说:绝大多数对象都是朝生夕灭的
- 强分代假说:熬过越多次垃圾收集的对象越难以消亡
二、Serial GC(串行收集器)
内存结构
┌────────────────────────────────────────┐
│ Single Thread │
│ ┌──────────────────────────────────┐ │
│ │ Young Generation │ │
│ │ ┌───────┬────┬────┐ │ │
│ │ │ Eden │ S0 │ S1 │ │ │
│ │ └───────┴────┴────┘ │ │
│ └──────────────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ Old Generation │ │
│ │ │ │
│ └──────────────────────────────────┘ │
└────────────────────────────────────────┘
执行原理
| 阶段 |
操作 |
说明 |
| 新生代 GC |
复制算法 |
单线程复制存活对象到 Survivor/Old |
| 老年代 GC |
标记-整理算法 |
标记存活 → 整理消除碎片 |
执行流程(STW):
1. 暂停所有应用线程
2. 新生代:Eden + From Survivor → To Survivor
3. 老年代:标记存活对象 → 整理内存
4. 恢复应用线程
优缺点
| 优点 |
缺点 |
| 简单高效(单核) |
单线程,多核利用率低 |
| 内存占用小 |
STW 时间长 |
| 适合客户端应用 |
不适合服务端 |
优化参数
-XX:+UseSerialGC
-XX:NewRatio=2
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=15
适用场景
- 单核机器
- 小内存应用(< 100MB)
- 客户端应用
三、Parallel GC(并行收集器 / Throughput Collector)
内存结构
┌────────────────────────────────────────┐
│ Multiple Threads │
│ ┌──────────────────────────────────┐ │
│ │ Young Generation │ │
│ │ ┌───────┬────┬────┐ │ │
│ │ │ Eden │ S0 │ S1 │ ← 多线程复制│ │
│ │ └───────┴────┴────┘ │ │
│ └──────────────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ Old Generation │ │
│ │ ← 多线程整理 │ │
│ └──────────────────────────────────┘ │
└────────────────────────────────────────┘
执行原理
Parallel Scavenge(新生代):
┌─────────────────────────────────────────┐
│ Thread1: Eden区扫描 → 复制存活对象 │
│ Thread2: Eden区扫描 → 复制存活对象 │
│ Thread3: Eden区扫描 → 复制存活对象 │
│ Thread4: Survivor扫描 → 复制存活对象 │
│ ... │
└─────────────────────────────────────────┘
Parallel Old(老年代):
多线程执行标记-整理算法
核心特点
| 特性 |
说明 |
| 关注吞吐量 |
CPU 运行用户代码时间 / 总时间 |
| 自适应调节 |
可自动调整新生代大小 |
| 并行执行 |
多线程垃圾收集 |
优化参数
-XX:+UseParallelGC
-XX:+UseParallelOldGC
-XX:GCTimeRatio=99
-XX:MaxGCPauseMillis=200
-XX:ParallelGCThreads=N
-XX:+UseAdaptiveSizePolicy
-XX:NewSize=256m
-XX:MaxNewSize=256m
适用场景
- 后台计算任务
- 批处理系统
- 科学计算
- 不需要交互的应用
四、CMS GC(并发标记清除)
内存结构
┌────────────────────────────────────────┐
│ │
│ ┌──────────────────────────────────┐ │
│ │ Young Generation │ │
│ │ ┌───────┬────┬────┐ │ │
│ │ │ Eden │ S0 │ S1 │ │ │
│ │ └───────┴────┴────┘ │ │
│ └──────────────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ Old Generation │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ 可能有内存碎片(不整理) │ │ │
│ │ └─────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
└────────────────────────────────────────┘
执行原理(4个阶段)
┌─────────────────────────────────────────────────────────────┐
│ CMS 老年代收集流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Initial Mark(初始标记)- STW │
│ └─ 标记 GC Roots 直接关联的对象 │
│ 速度很快,停顿短 │
│ │
│ 2. Concurrent Mark(并发标记) │
│ └─ 从 GC Roots 遍历整个对象图 │
│ 与用户线程并发执行 │
│ │
│ 3. Remark(重新标记)- STW │
│ └─ 修正并发标记期间变动的对象 │
│ 使用增量更新 │
│ │
│ 4. Concurrent Sweep(并发清除) │
│ └─ 清除标记为死亡的对象 │
│ 与用户线程并发执行 │
│ │
└─────────────────────────────────────────────────────────────┘
三色标记算法
┌────────────────────────────────────────┐
│ 三色标记法 │
├────────────────────────────────────────┤
│ ⚪ 白色:未被访问,可能是垃圾 │
│ ⚫ 黑色:已访问,存活对象 │
│ 🔘 灰色:已访问,但引用未扫描完 │
├────────────────────────────────────────┤
│ 扫描流程: │
│ 白色 → 灰色 → 黑色 │
│ 最终白色对象即为垃圾 │
└────────────────────────────────────────┘
CMS 的对象消失问题
问题场景(并发标记期间):
1. 黑色对象新增了对白色对象的引用
2. 灰色对象删除了对白色对象的引用
→ 白色对象被错误回收!
CMS 解决方案:增量更新
- 记录新增的引用关系
- 重新标记阶段重新扫描
优缺点
| 优点 |
缺点 |
| 低延迟 |
内存碎片问题 |
| 并发收集 |
CPU 敏感 |
| 适合互联网应用 |
浮动垃圾 |
|
JDK 9 标记废弃,JDK 14 移除 |
优化参数
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:ConcGCThreads=N
-XX:CMSFullGCsBeforeCompaction=0
-XX:+UseCMSCompactAtFullCollection
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+CMSIncrementalMode
-XX:+CMSParallelRemarkEnabled
-XX:+CMSScavengeBeforeRemark
适用场景
- 互联网网站
- B/S 架构服务端
- 要求低延迟的应用
- JDK 8 及以下版本
五、G1 GC(Garbage First)
内存结构(Region 化)
┌─────────────────────────────────────────────────────────────┐
│ G1 Heap │
│ ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐ │
│ │ E │ E │ S │ S │ O │ O │ O │ O │ H │ H │ E │ │
│ └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘ │
│ ↑ ↑ ↑ ↑ │
│ Eden Survivor Old Humongous │
│ │
│ Region 大小:1MB ~ 32MB(2的幂次) │
│ 默认约 2048 个 Region │
└─────────────────────────────────────────────────────────────┘
图例:
E = Eden Region
S = Survivor Region
O = Old Region
H = Humongous Region(大对象,> Region 50%)
执行原理
┌─────────────────────────────────────────────────────────────┐
│ G1 收集模式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【Young GC】 │
│ └─ 回收所有 Eden + Survivor Region │
│ 复制存活对象到新 Survivor / Old │
│ │
│ 【并发标记周期】 │
│ 1. Initial Mark(初始标记)- STW,伴随 Young GC │
│ 2. Root Region Scan(根区域扫描)- 并发 │
│ 3. Concurrent Mark(并发标记)- 并发 │
│ 4. Remark(最终标记)- STW │
│ 5. Cleanup(清理)- STW │
│ │
│ 【Mixed GC】 │
│ └─ 回收部分老年代 Region(根据回收价值排序) │
│ │
│ 【Full GC】(退化) │
│ └─ 当 Mixed GC 无法跟上分配速度时触发 │
│ │
└─────────────────────────────────────────────────────────────┘
RSet(Remembered Set)
┌────────────────────────────────────────┐
│ RSet 结构 │
├────────────────────────────────────────┤
│ 每个 Region 维护一个 RSet │
│ 记录"谁引用了我" │
│ │
│ 作用: │
│ - 避免 Full Heap 扫描 │
│ - 只扫描 RSet 即可知道跨代引用 │
│ │
│ 实现: │
│ - PRT(Per Region Table) │
│ - Card Table │
└────────────────────────────────────────┘
SATB(Snapshot-At-The-Beginning)
G1 使用 SATB 解决并发标记问题:
原理:
- 在标记开始时创建快照
- 记录标记过程中的所有变更
- 最终标记时处理变更
与 CMS 增量更新对比:
┌─────────────┬──────────────────────────┐
│ CMS │ G1 │
├─────────────┼──────────────────────────┤
│ 增量更新 │ SATB │
│ 记录新增引用│ 记录删除引用 │
│ 重新扫描 │ 基于快照 │
│ 精度更高 │ 效率更高 │
└─────────────┴──────────────────────────┘
优缺点
| 优点 |
缺点 |
| 可预测停顿时间 |
内存占用更高 |
| 无内存碎片 |
小堆性能不如 CMS |
| Region 化管理 |
大对象处理有开销 |
| 并发标记 |
|
优化参数
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=4m
-XX:G1NewSizePercent=5
-XX:G1MaxNewSizePercent=60
-XX:InitiatingHeapOccupancyPercent=45
-XX:G1MixedGCCountTarget=8
-XX:G1HeapWastePercent=5
-XX:G1HeapRegionSize=32m
-XX:ConcGCThreads=4
-XX:ParallelGCThreads=8
适用场景
- JDK 9+ 默认 GC
- 大堆内存(> 4GB)
- 要求可预测停顿时间
- 多核服务器
六、ZGC(Z Garbage Collector)
内存结构
┌─────────────────────────────────────────────────────────────┐
│ ZGC Heap │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ZPages │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │Small 2MB│ │Medium 32MB│ │Large N×2MB│ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 支持最大堆:16TB │
│ 停顿时间目标:< 10ms(实际 < 1ms) │
└─────────────────────────────────────────────────────────────┘
Page 类型:
- Small Page:2MB,存放小于 256KB 的对象
- Medium Page:32MB,存放 256KB ~ 4MB 的对象
- Large Page:N × 2MB,存放大于 4MB 的对象
核心技术
┌────────────────────────────────────────────────────────────┐
│ ZGC 核心技术 │
├────────────────────────────────────────────────────────────┤
│ │
│ 1. 着色指针 │
│ ┌─────────────────────────────────────┐ │
│ │ 64位指针: │ │
│ │ [未使用 16位][颜色位 4位][对象地址 42位]│ │
│ │ ↑ │ │
│ │ Finalizable / Remapped / │ │
│ │ Marked1 / Marked0 │ │
│ └─────────────────────────────────────┘ │
│ 颜色位标记对象状态,无需额外数据结构 │
│ │
│ 2. 读屏障 │
│ ┌─────────────────────────────────────┐ │
│ │ if (指针颜色 != 期望颜色) { │ │
│ │ 自愈处理; │ │
│ │ } │ │
│ └─────────────────────────────────────┘ │
│ 在读取引用时检查并修正,实现并发转移 │
│ │
│ 3. 多重映射 │
│ 同一物理内存映射到多个虚拟地址 │
│ 解决颜色位切换问题 │
│ │
└────────────────────────────────────────────────────────────┘
执行流程
┌─────────────────────────────────────────────────────────────┐
│ ZGC 收集阶段 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 标记开始 - STW(< 1ms) │
│ └─ 标记 GC Roots 直接引用 │
│ │
│ 2. 并发标记 │
│ └─ 遍历对象图,利用读屏障 │
│ │
│ 3. 标记结束 - STW(< 1ms) │
│ └─ 处理少量遗漏 │
│ │
│ 4. 并发转移准备 │
│ └─ 分析需要转移的 Region │
│ │
│ 5. 转移开始 - STW(< 1ms) │
│ └─ 初始化转移 │
│ │
│ 6. 并发转移 │
│ └─ 转移存活对象,利用读屏障 │
│ │
│ 总 STW 时间:< 10ms(通常 < 1ms) │
│ │
└─────────────────────────────────────────────────────────────┘
优缺点
| 优点 |
缺点 |
| 极低延迟(< 10ms) |
JDK 15 前不支持压缩指针 |
| 支持超大堆(16TB) |
内存占用较高 |
| 无碎片 |
吞吐量略低于 Parallel |
| 并发转移 |
JDK 版本要求高 |
优化参数
-XX:+UseZGC
-XX:ConcGCThreads=4
-Xmx16g
-XX:+UseZGC -XX:+ZGenerational
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
适用场景
- 超大堆内存(TB 级别)
- 要求极低延迟(< 10ms)
- JDK 15+ 生产环境
- 金融交易、实时系统
七、Shenandoah GC
内存结构
┌─────────────────────────────────────────────────────────────┐
│ Shenandoah Heap │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Regions (无分代) │ │
│ │ ┌────┬────┬────┬────┬────┬────┬────┬────┐ │ │
│ │ │ R │ R │ R │ R │ R │ R │ R │ R │ │ │
│ │ └────┴────┴────┴────┴────┴────┴────┴────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Region 大小:与 G1 类似 │
│ 停顿时间目标:< 10ms │
└─────────────────────────────────────────────────────────────┘
核心技术
┌────────────────────────────────────────────────────────────┐
│ Shenandoah 核心技术 │
├────────────────────────────────────────────────────────────┤
│ │
│ 1. 转发表 │
│ ┌─────────────────────────────────────┐ │
│ │ 记录对象转移映射 │ │
│ │ 旧地址 → 新地址 │ │
│ └─────────────────────────────────────┘ │
│ │
│ 2. Brooks 指针 │
│ ┌─────────────────────────────────────┐ │
│ │ 每个对象头增加转发指针 │ │
│ │ 正常指向自己,转移后指向新位置 │ │
│ └─────────────────────────────────────┘ │
│ │
│ 3. 读/写屏障 │
│ ┌─────────────────────────────────────┐ │
│ │ 读取时检查转发指针 │ │
│ │ 实现并发转移 │ │
│ └─────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────┘
执行流程
┌─────────────────────────────────────────────────────────────┐
│ Shenandoah 收集阶段 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Initial Mark - STW │
│ └─ 标记 GC Roots │
│ │
│ 2. Concurrent Marking │
│ └─ 并发标记整个堆 │
│ │
│ 3. Final Mark - STW │
│ └─ 完成标记,计算回收集合 │
│ │
│ 4. Concurrent Evacuation │
│ └─ 并发转移存活对象 │
│ │
│ 5. Init Update Refs - STW │
│ └─ 初始化引用更新 │
│ │
│ 6. Concurrent Update References │
│ └─ 并发更新引用 │
│ │
│ 7. Final Update Refs - STW │
│ └─ 完成引用更新 │
│ │
└─────────────────────────────────────────────────────────────┘
ZGC vs Shenandoah 对比
| 特性 |
ZGC |
Shenandoah |
| 实现方式 |
着色指针 + 读屏障 |
转发表 + Brooks 指针 |
| 内存开销 |
较低 |
较高(转发表) |
| JDK 支持 |
JDK 15+ 生产 |
JDK 12+ 生产 |
| 最大堆 |
16TB |
无限制 |
| OpenJDK |
Oracle 主导 |
Red Hat 主导 |
| 分代支持 |
JDK 21+ 支持 |
JDK 21+ 支持 |
优化参数
-XX:+UseShenandoahGC
-XX:ConcGCThreads=4
-Xmx8g
-XX:ShenandoahGuaranteedGCInterval=60000
适用场景
- 低延迟要求
- JDK 12+ 环境
- 大堆内存
- 对 Oracle ZGC 有顾虑的场景
八、GC 对比总结
性能对比
┌──────────────────────────────────────────────────────────────┐
│ GC 性能对比图 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 停顿时间 │
│ ↑ │
│ │ ████████████████████████ Serial GC │
│ │ ████████████████ Parallel GC │
│ │ ████████████ CMS │
│ │ ████████ G1 │
│ │ ████ Shenandoah │
│ │ ██ ZGC │
│ └─────────────────────────────────────────────────────────→ │
│ │
│ 吞吐量 │
│ ↑ │
│ │ ████████████████████████ Parallel GC │
│ │ ████████████████████ G1 │
│ │ ████████████████ ZGC │
│ │ ████████████████ Shenandoah │
│ │ ████████████ CMS │
│ │ ████████ Serial │
│ └─────────────────────────────────────────────────────────→ │
│ │
└──────────────────────────────────────────────────────────────┘
选择指南
┌─────────────────────────────────────────────────────────────┐
│ GC 选择决策树 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 堆内存大小? │
│ │ │
│ ├─ < 100MB ───────────────────→ Serial GC │
│ │ │
│ ├─ 100MB ~ 4GB ───────────────→ G1 GC / Parallel GC │
│ │ │
│ └─ > 4GB ─────────────────────→ G1 / ZGC / Shenandoah │
│ │
│ 延迟要求? │
│ │ │
│ ├─ 无要求(批处理)───────────→ Parallel GC │
│ │ │
│ ├─ < 200ms ───────────────────→ G1 GC │
│ │ │
│ └─ < 10ms ────────────────────→ ZGC / Shenandoah │
│ │
│ JDK 版本? │
│ │ │
│ ├─ JDK 8 ─────────────────────→ CMS / G1 / Parallel │
│ │ │
│ ├─ JDK 11 ────────────────────→ G1 │
│ │ │
│ └─ JDK 17+ ───────────────────→ G1 / ZGC │
│ │
└─────────────────────────────────────────────────────────────┘
各 GC 适用场景汇总
| GC |
JDK 版本 |
堆大小 |
延迟要求 |
典型场景 |
| Serial |
所有 |
< 100MB |
无要求 |
客户端、小应用 |
| Parallel |
JDK 8 默认 |
中等 |
无要求 |
批处理、计算任务 |
| CMS |
JDK 8- |
中等 |
中等 |
互联网服务(已废弃) |
| G1 |
JDK 9+ 默认 |
> 4GB |
< 200ms |
服务端通用 |
| ZGC |
JDK 15+ |
> 8GB |
< 10ms |
金融、实时系统 |
| Shenandoah |
JDK 12+ |
大堆 |
< 10ms |
低延迟服务 |
关键记忆点
| GC |
核心算法 |
核心特点 |
一句话总结 |
| Serial |
复制 + 标记整理 |
单线程、简单 |
小内存首选 |
| Parallel |
复制 + 标记整理 |
多线程、吞吐量优先 |
批处理首选 |
| CMS |
复制 + 标记清除 |
并发、低延迟 |
JDK 8 低延迟(已废弃) |
| G1 |
Region + 复制 |
可预测停顿、无碎片 |
JDK 9+ 默认,服务端通用 |
| ZGC |
着色指针 + 读屏障 |
极低延迟、大堆 |
JDK 15+ 低延迟首选 |
| Shenandoah |
转发表 + Brooks 指针 |
低延迟 |
ZGC 替代方案 |
九、GC 调优实战
通用调优步骤
1. 确定目标
├─ 吞吐量优先?→ Parallel GC
├─ 延迟优先?→ G1 / ZGC
└─ 内存小?→ Serial GC
2. 监控分析
├─ GC 日志分析
├─ JMX 监控
└─ 工具:GCViewer、GCEasy、JVisualVM
3. 调整参数
├─ 堆大小
├─ 新生代比例
└─ GC 线程数
4. 验证效果
├─ 压力测试
├─ 对比调优前后
└─ 持续监控
GC 日志配置
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xloggc:/path/to/gc.log
-Xlog:gc*:file=/path/to/gc.log:time,level,tags
-Xlog:gc*,gc+heap=debug:file=gc.log
-Xlog:gc+cause:file=gc.log
常见问题排查
┌─────────────────────────────────────────────────────────────┐
│ 常见 GC 问题 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Full GC 频繁 │
│ 原因:堆太小 / 内存泄漏 / 大对象 │
│ 解决:增大堆 / 排查泄漏 / 调整大对象阈值 │
│ │
│ 2. Young GC 频繁 │
│ 原因:新生代太小 / 对象过早晋升 │
│ 解决:增大新生代 / 调整晋升阈值 │
│ │
│ 3. GC 停顿时间长 │
│ 原因:堆太大 / 使用了 Serial GC │
│ 解决:换 G1/ZGC / 减小堆 / 增加并发线程 │
│ │
│ 4. 内存碎片 │
│ 原因:CMS 不整理 / 对象大小不均 │
│ 解决:换 G1 / 定期触发 Full GC 整理 │
│ │
│ 5. Metaspace/PermGen 溢出 │
│ 原因:类加载过多 / 动态代理 │
│ 解决:增大 Metaspace / 排查类加载器泄漏 │
│ │
└─────────────────────────────────────────────────────────────┘
学习建议
- 先理解基础:分代、GC Roots、三色标记
- 重点掌握 G1:当前主流,面试必问
- 了解 ZGC:未来趋势,大厂在用
- 实践调优:配置 GC 日志,分析实际问题
所有评论(0)