适用版本:Java 8 ~ Java 21 | 核心目标:理解 GC 算法演进、选型策略与性能调优
关键认知:没有“最好”的 GC,只有“最合适”业务场景的 GC
一、JVM 内存布局回顾(GC 作用域)

垃圾回收(GC)主要作用于堆内存。简单回顾一下,GC 通常分为两类:
- Young GC(Minor GC):清理新生代(Eden区和Survivor区)。
- Full GC(Major GC):清理整个堆(新生代 + 老年代)以及元空间(Metaspace),通常伴随着长时间的 Stop-The-World(STW)暂停。
- GC Roots:是一组必须存活的对象的起点,例如线程栈中的局部变量、静态变量、JNI引用等,是垃圾标记过程的根源。
二、垃圾回收算法基础
现代垃圾回收器的实现,都基于以下几种经典算法:
| 算法 |
原理 |
优点 |
缺点 |
| 标记-清除 |
标记所有存活对象 → 清除未标记对象 |
实现简单 |
产生内存碎片 |
| 复制算法 |
将存活对象从 Eden+From 区复制到 To 区 |
无内存碎片、效率高 |
内存利用率仅50% |
| 标记-整理 |
标记存活对象后,将它们向内存一端移动 |
无内存碎片 |
对象移动成本高 |
现代 GC 如何组合使用这些算法?
- 新生代:通常采用复制算法(Eden/Survivor 区设计即为此服务)。
- 老年代:则采用标记-整理(如G1、ZGC)或标记-清除(如已废弃的CMS)。
三、主流垃圾回收器详解(按时间线演进)
JVM 中的垃圾回收器并非单一实现,而是一个随着版本演进的“家族”。
✅ 1. Serial GC(单线程回收器)
- 适用场景:单核 CPU、堆内存极小(<100MB)的客户端或嵌入式应用。
- 特点:
- 新生代:Serial(复制算法)
- 老年代:Serial Old(标记-整理算法)
- 全程单线程工作,GC时暂停所有应用线程(STW)。
- 启动参数:
-XX:+UseSerialGC
- STW 时间:长。
典型用途:嵌入式设备、简单的测试环境。
✅ 2. Parallel GC(吞吐量优先回收器)
- 别名:Throughput Collector。
- 适用场景:后台计算、批处理任务,追求高吞吐量(应用程序运行时间占比高)。
- 特点:
- 新生代:Parallel Scavenge(多线程复制)
- 老年代:Parallel Old(多线程标记-整理)
- 启动参数:
-XX:+UseParallelGC(Java 8 的默认 GC)
- 核心调优参数:
-XX:MaxGCPauseMillis=200 # 期望达到的最大GC停顿时间(毫秒)
-XX:GCTimeRatio=99 # 吞吐量目标,计算公式:99/(1+99) = 99%
注意:从 Java 9 开始,默认的垃圾回收器已更改为 G1 GC。
✅ 3. CMS(Concurrent Mark Sweep)—— 低延迟先驱(已废弃)
- 设计目标:减少 STW 时间,特别适合对响应时间敏感的 Web 应用。
- 四阶段回收过程:
- Initial Mark(STW):初始标记,只标记 GC Roots 直接关联的对象,速度很快。
- Concurrent Mark(并发):并发标记,遍历整个对象图。
- Remark(STW):重新标记,修正并发标记期间因程序运行而导致变动的标记。
- Concurrent Sweep(并发):并发清除垃圾对象。
- 致命缺陷:
- 内存碎片:标记-清除算法导致,可能最终触发一次耗时的 Full GC(由 Serial Old 执行)。
- CPU 敏感:并发阶段会占用额外的 CPU 资源(通常1~2核)。
- 废弃时间:Java 14 中正式移除,Java 9+ 已不推荐使用。
替代方案:G1 GC 或 ZGC。
✅ 4. G1 GC(Garbage-First)—— 平衡之选
- 设计目标:在吞吐量和延迟之间取得平衡,并提供可预测的停顿时间。
- 核心创新:
- 堆分区(Region):将堆划分为多个大小固定(通常1MB~32MB)的区域。
- 记忆集(Remembered Set, RSet):记录跨 Region 的引用关系,避免全堆扫描。
- Mixed GC:不仅回收年轻代Region,还选择性回收一部分老年代Region(回收价值高的优先)。
- 启动参数:
-XX:+UseG1GC(Java 9 ~ Java 21 的默认 GC)
- 关键调优参数:
-XX:MaxGCPauseMillis=200 # 目标停顿时间(默认200ms)
-XX:G1HeapRegionSize=16m # 手动指定 Region 大小
-XX:G1MixedGCCountTarget=8 # 设置 Mixed GC 的预期最大轮次
G1 回收过程

适用场景:
- 堆内存从 6GB 到数百 GB 的中大型应用。
- 要求 GC 停顿时间小于 0.5 秒的电商、金融等业务系统。
✅ 5. ZGC(Z Garbage Collector)—— 超低延迟王者
- 目标:实现停顿时间不超过 10 毫秒,且与堆大小无关(TB级堆亦然)。
- 核心技术:
- 着色指针(Colored Pointers):在64位指针中嵌入少量元数据(标记、重映射、可终结状态),用于并发标记和转移。
- 读屏障(Load Barrier):在应用程序线程读取对象指针时,由JVM插入一小段代码,用于修正并发转移后的对象地址。
- 全并发处理:标记、转移(压缩)、重定位等核心阶段几乎全部并发执行。
- 启动参数:
-XX:+UseZGC(Java 15+ 已宣告生产就绪)
- 优势:
- 极低且可预测的停顿时间。
- 相比 Parallel GC,吞吐量损失仅约15%。
- 限制:
- 需要 Linux 内核版本 ≥ 4.15(以支持 multi-mapping 内存映射技术)。
- 暂不支持压缩类指针空间(Compressed Class Pointers)。
适用场景:
- 对延迟极度敏感的实时系统,如游戏服务器、高频交易平台。
- 超大内存(>100GB)的 Java 应用。
✅ 6. Shenandoah GC —— OpenJDK 的低延迟方案
与 ZGC 目标相似,但由 Red Hat 主导开发并贡献给 OpenJDK。
| 特性 |
ZGC |
Shenandoah |
| 主要支持方 |
Oracle JDK |
OpenJDK(Red Hat 主导) |
| 指针技术 |
着色指针 |
Brooks Pointer(转发指针) |
| 最低生产就绪 JDK |
15 |
12(实验性),15+ |
| 性能表现 |
通常更优 |
略逊于 ZGC,但仍非常出色 |
- 启动参数:
-XX:+UseShenandoahGC
四、GC 选型决策矩阵
那么,面对这些各具特色的回收器,我们该如何选择呢?你可以参考下面的决策矩阵:
| 业务场景 |
推荐 GC |
典型堆大小 |
停顿要求 |
建议 JDK 版本 |
| 微服务/小型应用 |
Parallel GC |
<4GB |
<1s |
Java 8 |
| 中大型 Web 应用 |
G1 GC |
4GB~100GB |
<0.5s |
Java 8u40+ |
| 超低延迟系统 |
ZGC |
>16GB |
≤10ms |
Java 15+ |
| OpenJDK 用户(需低延迟) |
Shenandoah |
>8GB |
≤10ms |
Java 15+ |
| 嵌入式/IoT设备 |
Serial GC |
<512MB |
无特殊要求 |
任意 |
各Java版本的默认GC变迁:
- Java 8:Parallel GC
- Java 9 ~ 13:G1 GC
- Java 14+:G1 GC(ZGC 和 Shenandoah 需要显式通过参数开启)
五、GC 日志分析与调优实战
理论最终要服务于实践。要调优,首先要学会观察。
1. 开启 GC 日志(Java 9+ 统一日志框架)
-Xlog:gc*:file=gc.log:time,tags
# 或开启更详细的日志模式
-Xlog:gc*,gc+age=trace,safepoint:file=gc.log:time
2. 关键指标解读与健康标准
| 监控指标 |
健康值参考 |
风险信号与可能原因 |
| Young GC 频率 |
1~10 次/分钟 |
>20次/分钟 → Eden区可能设置过小 |
| Young GC 平均耗时 |
<50ms |
>100ms → 对象过早晋升或Survivor区溢出 |
| Old GC / Mixed GC 频率 |
<1次/小时 |
频繁发生 → 老年代对象增长过快,或存在内存泄漏 |
| Allocation Stall(分配停顿) |
0 |
>0 → 内存分配发生阻塞(ZGC特有指标) |
3. 常见问题与调优思路
问题一:频繁触发 Full GC
- 常见原因:老年代内存碎片(CMS时代)、元空间(Metaspace)不足、大对象分配失败。
- 解决思路:
问题二:G1 Mixed GC 效率低下
六、未来趋势浅析
GC技术的发展从未停止:
- Project Lilliput:旨在将Java对象头从128位压缩至64位,大幅减少内存占用,从而间接降低GC的压力和频率。
- Unified GC:一个长期愿景,希望整合G1、ZGC和Shenandoah的代码库,为开发者提供更统一、灵活的配置接口。
总结:GC 选择黄金法则
“先明确业务 SLO(服务水平目标),再选择 GC。”
将这句话落到实处,可以遵循以下路径:
- 追求极致吞吐量 → Parallel GC。
- 寻求吞吐与延迟的平衡 → G1 GC(目前大部分场景的安心之选)。
- 要求超低延迟(亚秒级甚至毫秒级) → ZGC(Oracle JDK) 或 Shenandoah(OpenJDK)。
- 永远保持监控:使用 Prometheus + Grafana 等工具对 GC 频率、耗时、内存使用率进行可视化监控。
- 不要盲目追新:例如,ZGC 在处理小堆(<8GB)场景时,其收益可能不如成熟的 G1。
附录:常用故障排查命令速查
# 查看指定Java进程当前使用的GC
jinfo -flag UseG1GC <pid>
# 以1秒间隔持续监控GC概况(百分比)
jstat -gcutil <pid> 1000
# 生成堆内存转储文件(用于分析内存泄漏)
jmap -dump:format=b,file=heap.hprof <pid>
理解和掌握JVM垃圾回收器,是每一位Java后端开发者向高阶进阶的必修课。希望这篇指南能帮助你建立起清晰的认知框架。如果你在实践中有更深入的见解或疑问,欢迎在云栈社区与大家交流探讨。
|