Java GC演进史:从Serial到ZGC的垃圾收集器进化之路

Java的垃圾收集器(GC)演进史,就是一部在吞吐量(应用工作时间/总时间)和延迟(GC暂停应用的时间)之间不断寻求最佳平衡的历史。

迄今为止,Java共有7款主流GC,它们各自诞生于不同年代,解决了不同场景下的核心痛点。

🗺️ Java GC 核心参数与支持版本一览

这里整理了一份速查表,涵盖了从JDK 1.0到最新版本的关键信息:

GC名称 核心参数 ( -XX:+Use... ) 核心原理与特点 首次支持版本 默认使用版本
Serial SerialGC 单线程收集,GC时 STW (Stop-The-World,即暂停所有应用线程)。 JDK 1.0 JDK 1.0 - 1.3.1(客户端模式默认)
Parallel (Parallel Scavenge) ParallelGC
ParallelOldGC
多线程 收集,关注 高吞吐量 ,GC时STW。 JDK 1.4 JDK 7 & 8 默认 (Server模式)
CMS (Concurrent Mark Sweep) ConcMarkSweepGC 并发 收集(标记、清除阶段与应用线程并发),追求 低延迟 ,算法产生内存碎片,且已 废弃 (Deprecated)。 JDK 1.4 JDK 9前可选(JDK 14后被彻底移除)
G1 (Garbage First) G1GC 将堆划分为 Region ,优先回收垃圾最多的Region(Garbage First),实现 可预测的停顿时间模型 JDK 7 (u4) JDK 9及之后默认 (直到JDK 21)
Shenandoah ShenandoahGC 并发压缩 ,与ZGC类似追求超低延迟,但GC动作(尤其是压缩)与应用线程并发执行。 JDK 12 (实验) JDK 15 (生产)
非默认,需手动启用
ZGC (Z Garbage Collector) ZGC 基于 染色指针读屏障 ,将最耗时的GC操作几乎全部并发化, 停顿时间不随堆大小增长 (<1ms)。 JDK 11 (实验) JDK 15 (生产)
JDK 21 (支持分代)
非默认,需手动启用
Epsilon (No-Op) EpsilonGC 只分配内存,不做任何回收 (模拟内存耗尽),纯测试用途。 JDK 11 非默认,需手动启用

💡 小贴士STW 是GC中最让人头疼的部分,它意味着在垃圾回收期间,所有应用线程都会被暂停,导致程序出现卡顿。


⏳ 各核心GC详解与演进历程

理解了上面的表格,我们再沿着时间线,深度剖析一下这几款GC的兴衰更替。

1. 黎明期:单兵作战的 Serial GC (JDK 1.0)

在Java诞生之初,硬件资源匮乏,Serial GC作为第一款GC登场了。它的工作方式非常”简单粗暴”:进行垃圾回收时,它会独占所有CPU资源,冻结所有应用线程(STW),直到回收结束。

适用场景:由于它只有单一线程,没有线程交互的开销,在单核处理器或内存小于100MB的嵌入式/客户端应用中反而最高效。

现状:至今仍然被保留,用于特定的资源受限环境。


2. 吞吐量时代:团队作战的 Parallel GC (JDK 1.4 - JDK 8)

随着多核CPU的普及,人们迫切希望利用多核能力。Parallel GC(也称为吞吐量优先收集器)应运而生。它开启了多线程并行回收的时代,利用多个GC线程同时工作,大大缩短了STW时间。

核心目标:最大化应用程序的运行时间占比(即吞吐量)。它非常适合在后台进行计算、批处理、报表分析等不需要频繁与用户交互的任务。

历史地位:它曾是JDK 7和JDK 8的默认GC,也是许多后端程序员最熟悉的”老朋友”。


3. 低延迟首秀:追求并发与碎片化的 CMS GC (JDK 1.4 - JDK 14)

当Java应用进入Web时代(如电商、交易系统),用户无法忍受长时间的页面卡顿。CMS GC为了解决延迟问题,引入了一种革命性的思想——并发

它将GC过程拆分为多步,让最耗时的”标记”和”清除”阶段与应用程序同时运行,从而极大地减少了STW时间。

历史贡献:它是第一款真正意义上实现”低延迟”的GC,在JDK 1.4到JDK 8期间广受欢迎。

致命缺陷:然而,它的算法是”标记-清除”,这会导致内存碎片化,最终引发更严重的并发模式失败(CMS退化为Serial GC进行Full GC,反而造成更长的停顿)。

最终结局:由于设计上的复杂性难以彻底解决,Oracle在 JDK 9中将其标记为废弃(Deprecated),并在 JDK 14中正式将其移除


4. 分区域王者:掌控全局的 G1 GC (JDK 7至今)

为了完全取代CMS,G1 GC诞生了。它颠覆了传统GC将堆内存物理划分为”新生代”和老年代的做法,而是将整个堆划分为多个大小相等的Region(区域)

核心优势

  • 可预测的停顿模型:你可以通过参数 -XX:MaxGCPauseMillis=200 设定一个期望的停顿时间(比如200ms),G1会尽量在这个时间约束内,优先回收垃圾最多的Region(Garbage First),实现”价值最大化”。
  • 统一管理:Region可以动态地在Eden、Survivor、Old等角色间切换,内存管理更灵活。

历史地位:从 JDK 9开始,G1正式取代Parallel成为默认GC,并作为主力一直沿用到JDK 21之后的版本。


5. 超低延迟双雄:ZGC 与 Shenandoah (JDK 11/15至今)

在大内存(超过100GB)和实时响应的场景下(如金融风控、AI在线推理),即使是G1的几十毫秒停顿也可能成为瓶颈。ZGC和Shenandoah瞄准的目标是——将停顿时间缩短到10ms以内,且不随堆内存增大而增加

技术核心

  • ZGC:利用染色指针读屏障技术,几乎将所有的GC工作都并发执行,只保留了极少且极短的STW阶段。
  • Shenandoah:与ZGC类似,它在压缩(移动)对象的阶段也是并发的,这是它区别于G1和CMS的最大特点。

成熟之路:两者均在 JDK 11中以实验性功能出现(ZGC需解锁参数),并在 JDK 15中正式宣布生产可用。在 JDK 21中,ZGC进一步演进,引入了分代ZGC,性能再次提升。


6. 另类存在:Epsilon GC (No-Op)

最后值得一提的是Epsilon。它是一个不做任何垃圾回收的GC。当内存耗尽时,JVM就会直接崩溃。

使用场景:它只用于性能测试、压力测试或极端短暂的任务,用来排除GC本身带来的性能干扰,严禁用于生产环境


💎 总结与选型建议

GC的选择本质上是一种权衡。面对琳琅满目的收集器,可以遵循以下简单的决策路径:

如果是JDK 8及以下:

  • 默认的 Parallel GC 通常足够好。
  • 如果你的Web应用对延迟敏感(比如接口响应要求<50ms),尝试启用 G1 GC 并设置合理的 MaxGCPauseMillis

如果是JDK 9 - JDK 20:

  • 首选默认的G1 GC。它是这个时代最稳妥、兼顾吞吐量与延迟的选择。

如果是JDK 21及以上且追求极致性能:

  • 如果你的堆内存很大(>8GB)且对延迟极其敏感(如实时交易、游戏服务端),请毫不犹豫地尝试 ZGC(JDK 21后开启分代模式)。它已经非常成熟,能带来数量级的延迟改善。

📝 如果你是刚接触这个概念,记住一个简单的结论
现在用JDK 8就选G1,用JDK 11/17,默认的G1已经非常好,但值得试试ZGC。