Java的垃圾回收机制,从来不是一个“随便选个GC就能运行”的事情。你需要根据应用的实际场景来匹配回收逻辑——Serial适合单核小堆、嵌入式环境,ZGC瞄准超低延迟大堆,中间的Parallel、CMS、G1各自占据关键战场。选错了,性能下降是必然的。

以下逐一解析,帮助读者了解各GC的适用场景与避坑要点。
Serial GC:单线程回收,轻量但停顿明显
Serial是JVM中最早的垃圾回收器,全程单线程执行。新生代采用复制算法,老年代采用标记-整理算法,所有GC操作都会触发应用停顿(Stop-The-World),因此吞吐量较低、延迟较高。但其内存占用极小,启动速度快,特别适用于嵌入式设备或客户端桌面程序——早期IDEA启动参数默认即为Serial。启用参数:-XX:+UseSerialGC。
- 新生代分为Eden和两个Survivor区,对象优先在Eden区分配,GC时将存活对象复制到空闲Survivor区,随后交换角色。
- 老年代回收触发条件通常是新生代对象晋升失败,或老年代空间不足,此时整个应用暂停,执行标记-整理。
- 不适用于服务器端。一旦堆大小超过100MB,Full GC可能导致几百毫秒甚至秒级停顿,线上环境难以承受。
Parallel GC(吞吐量优先):多线程并行,适合批处理
Parallel GC也称为吞吐量收集器,通过多线程并行执行Minor GC和Full GC,目标是最大化应用吞吐量(即用户代码运行时间占比),而非响应速度。它默认启用自适应调优(-XX:+UseAdaptiveSizePolicy),动态调整新生代大小及Eden/Survivor比例。典型应用场景包括后台计算、定时任务等对延迟不敏感的任务。
- 新生代采用复制算法,老年代采用标记-整理算法,两者均全程STW,但由于多线程加速,总停顿时间较Serial大幅缩短。
- 可通过
-XX:MaxGCPauseMillis设置目标停顿时间,但Parallel并不保证达成,它只是尽力优化吞吐量。 - 不支持并发标记或并发清理,处理低延迟场景较为吃力。例如Web API响应需控制在100ms以内时,Parallel并不适合。
G1垃圾回收器:区域化分代,兼顾吞吐量与延迟控制
G1(Garbage-First)打破了传统分代的物理隔离,将堆划分为多个固定大小的Region(1–32MB),每个Region可动态扮演Eden、Survivor或Old角色。它采用增量式标记流程:Initial Mark → Root Region Scan → Concurrent Mark → Remark → Cleanup,大部分标记过程与用户线程并发执行。G1的目标是实现可控停顿(通过-XX:MaxGCPauseMillis设定),同时保持较高的吞吐量。
- Young GC是STW的复制回收,仅涉及Eden和Survivor Region;Mixed GC则在Young GC基础上,额外回收部分老年代Region。
- 当老年代占用率超过
-XX:InitiatingHeapOccupancyPercent(默认45%)时,触发并发标记周期。 - 容易出现“疏散失败”(Evacuation Failure)或“巨型对象分配失败”,需合理设置Region大小和初始堆占比。若调优不当,性能可能甚至低于CMS。
ZGC垃圾回收器:着色指针+读屏障,挑战毫秒级停顿极限
ZGC是JDK 11引入的低延迟垃圾回收器,设计目标十分激进:无论堆大小(可达TB级)、对象数量多少,停顿时间稳定控制在10ms以内。它不设分代,全堆统一管理,核心依赖于三大技术:着色指针(Pointer Coloring)、读屏障(Load Barrier)和并发处理(Concurrent Mark/Relocate/Remap)。ZGC的所有GC阶段(包括标记、转移、重映射)几乎全部并发执行,仅有两次极短的STW(<1ms)。
- 对象地址的高4位存储元信息(如Marked0/Marked1/Remapped),避免了额外的元数据表开销。
- 每次对象读取前插入读屏障,检查是否已被重定位。若对象已迁移,则自动更新本地引用并转发。
- 目前要求64位Linux系统,且堆大小建议≥8GB才能发挥优势。小堆场景下可能不如G1,因为读屏障存在微小开销。
