Java 垃圾回收策略概览
从 Parallel GC 到 ZGC,梳理 JDK 各版本默认 GC 及其核心思路,帮你快速建立 JVM 垃圾回收的整体认知。
为什么需要垃圾回收?
Java 把内存管理交给 JVM,开发者不需要手动 free()。
JVM 的垃圾回收器(GC)负责找出不再被引用的对象并释放它们占用的堆内存。
不同的 GC 在吞吐量(单位时间处理量)和停顿时间(Stop-The-World,STW)之间取舍不同,
随着 JDK 迭代,默认 GC 也在不断演进。
Parallel GC — JDK 7 / 8 默认
通过 -XX:+UseParallelGC 开启,JDK 7 和 JDK 8 均默认使用。
核心思路:
- 多线程并行执行——充分利用多核 CPU,大幅缩短 GC 时间(相比早期单线程的 Serial GC)。
- 年轻代:复制算法(Copying)——将存活对象从 Eden / Survivor 复制到另一块 Survivor,整理后无碎片。
- 老年代:标记-压缩(Mark-Sweep-Compact)——标记存活对象,清除死亡对象,再将存活对象紧凑排列消除碎片。
- 全程 STW——GC 期间所有业务线程暂停,停顿时间随堆大小线性增长,不适合对延迟敏感的场景。
适合批处理、后台计算等吞吐量优先、能容忍短暂停顿的应用。
CMS — 低停顿先行者(JDK 8)
Concurrent Mark Sweep,通过 -XX:+UseConcMarkSweepGC 在 JDK 8 中开启。
JDK 9 起标记为废弃(deprecated),JDK 14 正式移除。
核心思路:
- 老年代并发标记——大部分标记工作与业务线程并发执行,大幅减少停顿时间。
- 只有两个短暂 STW 阶段——初始标记(Initial Mark)和重新标记(Remark),耗时极短。
- 标记-清除,不压缩——不移动对象,因此会产生内存碎片,长时间运行后可能触发一次全量 STW 压缩(Serial Full GC)。
- CPU 占用较高——并发阶段与业务线程竞争 CPU,在核数少的机器上影响吞吐量。
适合对响应时间敏感、堆不超大(通常 < 4 GB)的 Web 服务。
G1 GC — JDK 9+ 默认
Garbage-First,从 JDK 9 起成为默认 GC,通过
-XX:+UseG1GC 显式指定。
核心思路:
- Region 分区——把堆划分为大量等大的 Region(默认 1–32 MB),不再严格区分连续的年轻代 / 老年代区域,每个 Region 可动态承担不同角色。
- 优先回收垃圾最多的 Region——"Garbage First" 由此得名,有限的 GC 时间用在收益最大的地方。
- 可预测停顿目标——通过
-XX:MaxGCPauseMillis设置期望停顿(默认 200ms),G1 自动调节每次回收的 Region 数量。 - 兼顾吞吐与延迟——在大堆(4–32 GB)场景下比 CMS 更稳定,也比 Parallel GC 停顿更短,是当前最通用的选择。
ZGC — 超低延迟(JDK 15+ 正式可用)
通过 -XX:+UseZGC 开启,JDK 21 起成为许多低延迟场景的首选。
核心思路:
- 几乎全程并发——标记、重定位、引用处理均与业务线程并发,STW 只剩极短的初始标记和转移阶段。
- 停顿时间 < 1ms——且基本不随堆大小增长,在数百 GB 大堆下依然保持毫秒级停顿。
- 染色指针(Colored Pointers)——利用 64 位指针的高位存储 GC 元数据,避免额外的内存开销和读屏障复杂度。
- 吞吐量略低——并发线程与业务线程共享 CPU,总体吞吐量不如 Parallel GC,但对延迟敏感应用来说这个代价值得。
适合实时交易、游戏服务器、大内存微服务等对毫秒级延迟有严格要求的场景。
横向对比
| GC | 默认版本 | 停顿 | 吞吐量 | 适用场景 |
|---|---|---|---|---|
| Parallel GC | JDK 7 / 8 | 高(全 STW) | ★★★★★ | 批处理、计算密集 |
| CMS | JDK 8 可选 | 低 | ★★★☆☆ | 低延迟 Web(已废弃) |
| G1 GC | JDK 9+ | 中(可预测) | ★★★★☆ | 通用、大堆服务 |
| ZGC | JDK 15+ | 极低(<1ms) | ★★★☆☆ | 超低延迟、超大堆 |
没有银弹——选对 GC 取决于你的堆大小、业务延迟要求和 JDK 版本。 大多数现代服务跑在 JDK 17 / 21 上,G1 GC 是最稳妥的起点, 如果 P99 延迟仍不达标,再考虑切换到 ZGC。