垃圾收集器详解
垃圾收集器概览
垃圾收集器是垃圾回收算法的具体实现。不同收集器各有侧重,适用于不同场景:
新生代收集器 老年代收集器 整堆收集器
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Serial │────→│ Serial Old │ │ │
├─────────────┤ ├─────────────┤ │ │
│ ParNew │────→│ CMS │ │ │
├─────────────┤ ├─────────────┤ │ │
│ Parallel │────→│ Parallel │ │ │
│ Scavenge │ │ Old │ │ │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ │ │ │ │ G1 │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ │ │ │ │ ZGC │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ │ │ │ │ Shenandoah │
└─────────────┘ └─────────────┘ └─────────────┘
Serial / Serial Old
最基础、最古老的收集器,使用单线程进行垃圾回收。
工作过程
用户线程 ──███──暂停──██████████──暂停──███──
Serial ────────→ GC ←──────────────→ GC ←──
- Serial(新生代):复制算法
- Serial Old(老年代):标记-整理算法
特点
- 单线程工作,GC 时必须暂停所有用户线程(Stop The World, STW)
- 实现简单,内存开销小
- 适合客户端模式或小内存应用
# 使用参数
-XX:+UseSerialGC # 同时使用 Serial + Serial Old
ParNew
Serial 的多线程版本,使用多个 GC 线程进行垃圾回收。
特点
- 多线程并发执行 GC(默认线程数 = CPU 核数)
- 除多线程外,其余行为与 Serial 完全一致
- 唯一能与 CMS 配合使用的新生代收集器
# 使用参数
-XX:+UseParNewGC # 使用 ParNew
-XX:ParallelGCThreads=4 # GC 线程数
Parallel Scavenge / Parallel Old
Parallel Scavenge(新生代)
与 ParNew 类似,但关注点是吞吐量(Throughput)而非停顿时间。
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + GC 时间)
自适应调节策略(GC Ergonomics):Parallel Scavenge 可根据当前系统运行情况动态调整新生代大小、Eden/Survivor 比例、对象晋升年龄等参数,以尽可能满足吞吐量目标。
# 核心参数
-XX:+UseParallelGC # 使用 Parallel Scavenge + Parallel Old
-XX:MaxGCPauseMillis=200 # 最大 GC 停顿时间目标(尽力满足)
-XX:GCTimeRatio=99 # 吞吐量大小(默认 99,即 GC 时间占 1%)
-XX:+UseAdaptiveSizePolicy # 自适应调节(默认开启)
-XX:ParallelGCThreads=4 # GC 线程数
Parallel Old(老年代)
Parallel Scavenge 的老年代搭档,使用标记-整理算法。JDK 6 开始提供,之前 Parallel Scavenge 只能搭配 Serial Old(严重影响吞吐量)。
CMS(Concurrent Mark Sweep)
CMS 以获取最短回收停顿时间为目标,基于标记-清除算法,适用于对响应时间敏感的应用。
四个阶段
1. 初始标记 (STW) ──短暂暂停,标记 GC Roots 直接关联的对象
2. 并发标记 ──与用户线程并发,进行 GC Roots Tracing
3. 重新标记 (STW) ──短暂暂停,修正并发标记期间变动的引用(增量更新)
4. 并发清除 ──与用户线程并发,清除未标记对象
用户线程 ──███──暂停──████████████████──暂停──████████████████──
CMS ────→初始标记──→ 并发标记 ──→重新标记──→ 并发清除 ──→
优缺点
优点:
- 并发收集,停顿时间短
- 适合对响应时间要求高的 Web 应用
缺点:
- CPU 敏感:并发阶段占用 CPU,默认启动 (CPU核数+3)/4 个 GC 线程
- 浮动垃圾:并发清除阶段用户线程产生的新垃圾,只能下次 GC 处理
- 内存碎片:标记-清除算法导致碎片,可能触发 Serial Old 整理
- Concurrent Mode Failure:老年代预留空间不足,退化为 Serial Old 全停顿
# CMS 参数
-XX:+UseConcMarkSweepGC # 使用 CMS
-XX:CMSInitiatingOccupancyFraction=75 # 老年代使用 75% 时触发 GC
-XX:+UseCMSCompactAtFullCollection # Full GC 后整理碎片
-XX:CMSFullGCsBeforeCompaction=5 # 5 次 Full GC 后整理
-XX:ConcGCThreads=2 # 并发 GC 线程数
-XX:+CMSParallelRemarkEnabled # 并行重新标记
CMS 已被弃用
- JDK 9 标记为 Deprecated
- JDK 14 正式移除
- 推荐迁移到 G1
G1(Garbage-First)
G1 是 JDK 9 的默认收集器,面向服务端应用,兼顾吞吐量和停顿时间。
Region 化内存布局
G1 打破传统的物理分代,将堆划分为大小相等的 Region(1-32MB,默认 2048 个):
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ E │ S │ O │ H │ E │ O │ E │ S │
├────┼────┼────┼────┼────┼────┼────┼────┤
│ O │ E │ E │ O │ E │ S │ O │ E │
└────┴────┴────┴────┴────┴────┴────┴────┘
E = Eden S = Survivor O = Old H = Humongous
每个 Region 可以是 Eden、Survivor、Old 或 Humongous(大对象)。G1 仍保留分代概念,但新生代和老年代不再是连续的内存区域。
回收模式
- Young GC:回收所有 Eden 和 Survivor Region
- Mixed GC:回收所有新生代 Region + 部分老年代 Region(回收价值高的优先)
- Full GC:退化为单线程整堆收集(应避免)
工作流程
1. Young GC ── 回收新生代(STW,多线程并行)
2. 并发标记 ── 类似 CMS 的四个阶段
3. Mixed GC ── 根据停顿预测模型选择回收的 Region
关键特性
- 可预测停顿:
-XX:MaxGCPauseMillis=200设置目标停顿时间,G1 根据历史数据预测选择回收哪些 Region - 回收价值优先(Garbage-First):优先回收垃圾最多的 Region
- 无碎片:整体基于标记-整理,局部(Region 之间)基于复制算法
- 大对象处理:超过 Region 大小一半的对象直接分配在 Humongous Region
# G1 参数
-XX:+UseG1GC # 使用 G1
-XX:MaxGCPauseMillis=200 # 目标停顿时间(默认 200ms)
-XX:G1HeapRegionSize=4m # Region 大小
-XX:InitiatingHeapOccupancyPercent=45 # 触发并发标记的堆占用率
-XX:G1MixedGCCountTarget=8 # Mixed GC 次数目标
-XX:G1MixedGCLiveThresholdPercent=85 # Region 存活率低于此值才回收
ZGC
ZGC(Z Garbage Collector)是 JDK 11 引入的低延迟收集器,目标是将 GC 停顿控制在 1ms 以内(JDK 16 之前为 10ms)。
核心技术
1. 着色指针(Colored Pointers)
在 64 位指针中借用几位存储 GC 元信息,避免修改对象头:
63 42 41 40 39 38 37 0
┌──────────┬──┬──┬──┬──┬──┬─────────────────────┐
│ 未使用 │R0│R1│F │R │P │ 对象地址 │
└──────────┴──┴──┴──┴──┴──┴─────────────────────┘
↑ ↑ ↑ ↑ ↑
Remap1 Remap Finalized Remapped
└──┘ Pinning
Relocation
2. 读屏障(Load Barrier)
在对象引用被加载时执行检查,如果指针着色表示对象已被移动,则自动修正引用:
Object o = obj.field; // 读屏障:检查 o 的指针颜色
// 如果 o 被标记为 "relocated",修正为新的地址
3. 并发整理
ZGC 实现了真正的并发整理——在移动对象的同时,用户线程可以继续运行。通过读屏障和着色指针配合,确保用户线程总是访问到正确的地址。
特点
- STW 时间 < 1ms,且不随堆大小增长
- 支持 TB 级堆内存
- 并发标记、并发转移、并发整理
- 无分代(JDK 21 之前),每次都是整堆收集
- JDK 21 引入分代 ZGC,大幅改善年轻代回收效率
# ZGC 参数
-XX:+UseZGC # 使用 ZGC
-XX:ZCollectionInterval=0 # GC 间隔(0 为自适应)
-XX:ZAllocationSpikeTolerance=2 # 分配突发容忍度
-XX:+UnlockDiagnosticVMOptions # 解锁诊断选项
-XX:+ZStatisticsForceTrace # 强制追踪统计
-XX:+UseZGC -XX:+ZGenerational # JDK 21+ 分代 ZGC
Shenandoah
Shenandoah 是 RedHat 开发的低延迟收集器,与 ZGC 目标类似但实现不同。
核心技术
Brooks Pointer:每个对象头部增加一个额外指针,指向对象自身。当对象被移动时,只需更新 Brooks Pointer,旧引用会通过间接跳转找到新地址。
移动前: 引用 → [对象 | Brooks Pointer → 自身]
移动后: 引用 → [旧位置 | Brooks Pointer → 新地址] → [对象 | Brooks Pointer → 自身]
特点
- GC 停顿与堆大小无关
- 使用读屏障 + 写屏障(比 ZGC 的纯读屏障开销略大)
- 有分代模式(JDK 22+)
- 在 OpenJDK 中可用,非 Oracle JDK 默认
# Shenandoah 参数
-XX:+UseShenandoahGC # 使用 Shenandoah
-XX:ShenandoahGCHeuristics=adaptive # 启发式算法
收集器选择策略
| 场景 | 推荐收集器 | 理由 |
|---|---|---|
| 客户端/小内存 | Serial | 简单高效,无线程开销 |
| 批处理/后台计算 | Parallel Scavenge + Parallel Old | 最大化吞吐量 |
| Web 服务(JDK 8) | ParNew + CMS | 低停顿 |
| Web 服务(JDK 9-14) | G1 | 平衡吞吐和延迟 |
| 低延迟要求(JDK 11+) | ZGC | 亚毫秒停顿 |
| 低延迟要求(非 Oracle JDK) | Shenandoah | 亚毫秒停顿 |
版本默认收集器
| JDK 版本 | 默认收集器 |
|---|---|
| JDK 8 | Parallel Scavenge + Parallel Old |
| JDK 9 - 20 | G1 |
| JDK 21+ | G1(ZGC 仍需手动启用) |
小结
本章详细介绍了从 Serial 到 ZGC 的各种垃圾收集器。选择收集器时需要在吞吐量和延迟之间权衡:Parallel 系列侧重吞吐量,CMS/G1 侧重延迟平衡,ZGC/Shenandoah 追求极致低延迟。下一章将学习如何通过 GC 日志分析收集器行为。
评论