Java随笔-GC收集器工作机制
一、GC 收集器分类总览
按回收区域分类
- 年轻代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:Serial Old、Parallel Old、CMS
- 整堆收集器:G1、ZGC、Shenandoah
按工作模式分类
| 模式 | 特点 | 代表 |
|---|---|---|
| 串行(Serial) | 单线程,全程 STW | Serial |
| 并行(Parallel) | 多线程,全程 STW,吞吐量优先 | Parallel Scavenge |
| 并发(Concurrent) | 部分阶段与应用线程并发,低停顿 | CMS、G1 |
| 并发整理(Concurrent Compacting) | 全阶段并发,几乎零停顿 | ZGC、Shenandoah |
2026 年 JDK 25 最新状态
- G1:JEP 523 成为所有环境默认(包括受限环境),Remembered Set 内存降低 62%
- ZGC:非分代模式已在 JDK 24 移除,仅保留分代 ZGC(-XX:+ZGenerational 不再需要)
- Shenandoah:分代模式在 JDK 25正式化,不再需要实验性标志
二、Serial / Serial Old(串行收集器)
适用场景
单核 CPU、客户端模式(Client VM)、内存较小(< 100MB)
参数
-XX:+UseSerialGC年轻代 Serial 回收流程(标记-复制算法)
STW 开始
├─ 单线程标记 Eden + From Survivor 中的存活对象
├─ 单线程复制存活对象到 To Survivor
├─ 清空 Eden + From Survivor
├─ From/To 交换角色
└─ 年龄达标(默认15)→ 晋升老年代
STW 结束
老年代 Serial Old 回收流程(标记-整理算法)
STW 开始
├─ 单线程标记所有存活对象
├─ 单线程整理:将存活对象向一端移动
└─ 清理边界外的内存
STW 结束
特点
- 单线程回收,实现简单,无线程切换开销
- STW 时间长(秒级),不适合服务端
- 无内存碎片(Serial Old 使用标记-整理)
- JDK 8 之前 Client 模式默认,现已被淘汰
三、Parallel / Parallel Old(并行收集器 / 吞吐量优先)
适用场景
后台计算、批处理、科学计算,追求最大吞吐量
参数
-XX:+UseParallelGC 或 -XX:+UseParallelOldGC
年轻代 Parallel Scavenge 回收流程(标记-复制算法)
STW 开始
├─ 多线程并行标记存活对象(线程数 = CPU 核心数)
├─ 多线程并行复制到 To Survivor
├─ 清空 Eden + From Survivor
└─ From/To 交换
STW 结束
老年代 Parallel Old 回收流程(标记-整理算法)
STW 开始
├─ 多线程并行标记存活对象
├─ 多线程并行整理(移动存活对象)
└─ 清理边界外内存
STW 结束
核心指标
吞吐量 = 用户代码运行时间 / (用户代码 + GC 时间)
特点
- 多线程并行,充分利用多核 CPU
- 吞吐量最高,适合 CPU 密集型任务
- 可自适应调节(-XX:+UseAdaptiveSizePolicy)
- STW 时间比 Serial 短,但仍需停顿
- JDK 8 Server 模式默认(Parallel Scavenge + Parallel Old)
关键参数
| 参数 | 说明 |
|---|---|
-XX:MaxGCPauseMillis=<n> | 目标最大停顿时间(JVM 会调整堆大小) |
-XX:GCTimeRatio=<n> | GC 时间占比目标(默认 99,即 1%) |
四、CMS(Concurrent Mark Sweep,并发标记清除)
适用场景
互联网应用、Web 服务,追求低停顿(< 1秒)
参数
-XX:+UseConcMarkSweepGC
搭配年轻代:ParNew(-XX:+UseParNewGC)
CMS 回收流程(老年代,标记-清除算法)
阶段 1:初始标记(Initial Mark)—— STW,极短
- 标记 GC Roots 直接关联的对象
- 时间 < 10ms(与 Minor GC 的 STW 复用)
阶段 2:并发标记(Concurrent Mark)—— 与应用线程并发
- 从 GC Roots 出发,遍历整个对象图
- 不 STW,用户线程继续运行
- 耗时较长(取决于堆大小和对象数量)
- 使用增量更新(Incremental Update)记录新增引用
阶段 3:重新标记(Remark)—— STW,较短
- 修正并发标记期间变动的标记
- 处理增量更新队列中的引用变化
- 时间比初始标记长,但远小于并发标记
阶段 4:并发清除(Concurrent Sweep)—— 与应用线程并发
- 清理已标记为垃圾的对象
- 不 STW,用户线程继续运行
- 耗时较长,但不停顿
时间线示意
CMS 的致命缺陷
1. 内存碎片
- 标记-清除算法产生大量碎片
- 大对象分配失败,触发 Full GC
- 缓解:-XX:+UseCMSCompactAtFullCollection(Full GC 时整理)
2. 浮动垃圾(Floating Garbage)
- 并发清理期间产生的新垃圾,本轮无法回收
- 需预留空间(-XX:CMSInitiatingOccupancyFraction,默认 68%)
- 预留不足 → Concurrent Mode Failure
3. Concurrent Mode Failure(并发模式失败)—— 最严重
- 并发标记期间,老年代空间被耗尽
- CMS 无法继续,退化为 Serial Old 单线程 Full GC
- 停顿时间剧增(秒级),用户体验极差
4. 对 CPU 敏感
- 并发阶段占用 CPU 资源,降低吞吐量
CMS 参数
| 参数 | 说明 |
|---|---|
-XX:CMSInitiatingOccupancyFraction=<n> | 老年代占用达 n% 时触发 CMS(默认 68) |
-XX:+UseCMSInitiatingOccupancyOnly | 只用设定阈值,不自动调整 |
-XX:+CMSParallelRemarkEnabled | 并行重新标记 |
-XX:+CMSScavengeBeforeRemark | 重新标记前触发 Minor GC |
五、G1(Garbage First,区域化分代收集器)
适用场景
大堆内存(> 4GB)、可预测停顿时间、平衡吞吐量和延迟
参数
-XX:+UseG1GC(JDK 9+ 默认,JDK 25 JEP 523 成为所有环境默认)
G1 核心设计
1. Region 化内存布局
- 将堆划分为多个 Region(1~32MB,默认 2048 个)
- 每个 Region 动态扮演不同角色:
- Eden Region:年轻代 Eden 区
- Survivor Region:年轻代 Survivor 区
- Old Region:老年代
- Humongous Region:大对象(> Region 的 50%),连续多个 Region 组成,直接分配在老年代区域
- Free Region:空闲区域
- Region 角色可在不同回收周期之间改变
2. Garbage First 策略
- 维护优先列表,按回收价值排序
- 回收价值 = 垃圾占比 / 回收耗时
- 每次选择回收价值最高的 Region,在有限时间内最大化回收量
3. 可预测停顿时间模型
- -XX:MaxGCPauseMillis(默认 200ms)
- G1 根据历史数据预测每个 Region 的回收耗时
- 选择 Region 组合,确保总停顿时间 < 目标值
G1 回收流程 —— Mixed GC
阶段 1:初始标记(Initial Marking)—— STW,很短
- 标记 GC Roots 直接关联的对象
- 修改 TAMS 指针(Top at Mark Start)
- 复用 Young GC 的 STW(piggyback,不额外停顿)
阶段 2:根区域扫描(Root Region Scanning)—— 并发
- 扫描 Survivor Region 中引用老年代的对象
- 必须在下一次 Young GC 之前完成
- 原因:Young GC 会移动 Survivor 对象,引用关系变化
阶段 3:并发标记(Concurrent Marking)—— 并发
- 遍历整个堆,标记所有存活对象
- 使用 SATB(Snapshot-At-The-Beginning) 算法
- 标记开始时创建存活对象快照
- 写屏障记录引用变更(旧值存入 SATB 队列)
- 保守但正确:可能多标记一些垃圾,但不会漏标存活对象
- 可被打断(Young GC 优先)
- TAMS 指针:Region 内 TAMS 之上对象默认存活,不标记
阶段 4:重新标记(Remark)—— STW
- 处理 SATB 队列中的引用变化
- 计算各 Region 的存活对象比例
- 时间较短
阶段 5:清理(Cleanup)—— 部分 STW
- 统计各 Region 垃圾占比
- 按回收价值排序
- 选择回收价值最高的 Region 组成回收集(Collection Set)
- 清空完全为空的 Region(非 STW)
- 重置已清理 Region
阶段 6:混合回收(Mixed GC)—— STW
- 复制 Eden + Survivor + 部分 Old Region 到新的 Region
- 标记-整理 + 复制混合
- 可多次执行(-XX:G1MixedGCCountTarget,默认 8)
- 每次回收部分老年代,控制单次停顿时间
- 清空旧 Region
G1 的 Remembered Set(RSet)
- 每个 Region 维护一个 RSet,记录哪些 Region 引用了本 Region 的对象
- 作用:避免全堆扫描,快速定位跨 Region 引用
- 更新:通过写屏障 + Dirty Card Queue 异步更新
- 应用线程将引用变更写入线程私有的 Dirty Card Queue
- Refinement 线程异步消费队列,更新 RSet
- 队列满时应用线程参与排空(拖慢分配,自我保护)
- JDK 25 优化:RSet 内存降低 62%(64GB 堆从 2GB 降至 0.75GB)
G1 的 Full GC
- 触发条件:并发标记失败、Mixed GC 后空间仍不足
- 使用单线程 Serial Old 算法(标记-整理)
- 停顿时间长,应尽量避免
G1 参数
| 参数 | 说明 |
|---|---|
-XX:MaxGCPauseMillis=<n> | 目标最大停顿时间(默认 200ms) |
-XX:G1HeapRegionSize=<n> | Region 大小(默认自动计算) |
-XX:G1NewSizePercent=<n> | 年轻代最小占比(默认 5%) |
-XX:G1MaxNewSizePercent=<n> | 年轻代最大占比(默认 60%) |
-XX:InitiatingHeapOccupancyPercent=<n> | 触发并发标记的堆占用率(默认 45%) |
-XX:G1MixedGCCountTarget=<n> | Mixed GC 目标次数(默认 8) |
-XX:G1ReservePercent=<n> | 预留内存比例(默认 10%) |
六、ZGC(The Z Garbage Collector,低延迟收集器)
适用场景
超大堆内存(TB 级)、极低延迟要求(< 1ms)、金融交易系统
参数
-XX:+UseZGC(JDK 11+ 实验性,JDK 15+ 正式,JDK 24+ 仅分代模式)
ZGC 核心设计 —— 三大黑科技
1. 染色指针(Colored Pointers)
- 利用 64 位指针的高 4 位存储元数据(第 42-45 位)
- 4 个标记位:
- Marked0(M0):第 42 位,标记阶段 0 的存活对象
- Marked1(M1):第 43 位,标记阶段 1 的存活对象
- Remapped(R):第 44 位,对象已转移到新地址
- Finalizable(F):第 45 位,对象有 finalize 方法
- 优势:
- 无需在对象头中存储标记位,节省内存
- 标记和转移可完全并发进行
- 转移完成后无需立即更新所有引用
- 限制:不支持压缩指针(Compressed Oops),内存开销 +15~30%
2. 读屏障(Load Barrier)
- 在读取对象引用时自动插入的代码
- 伪代码:
Objectload(Object*ref){if(ref has Remapped bit)returnref;Object*new_ref=get_forwarded_address(ref);if(CAS(ref,new_ref))returnnew_ref;returnload(ref);// 其他线程已更新,重读}- 关键特性:
- 仅在读操作时触发,写操作无需屏障
- 开销极低(现代 CPU 可预测分支优化)
- 实现无停顿并发转移:对象转移中仍可访问
- 与 Shenandoah 的 Brooks Pointer 对比:
- ZGC:读屏障 + 染色指针,无需对象头修改
- Shenandoah:Brooks Pointer 在对象头中,需要额外空间
3. 多重映射(Multi-Mapping)
- 将同一块物理内存映射到多个虚拟地址空间
- 支持染色指针的不同视图(M0/M1/R 视图)
- 对象转移时只需更新映射,无需复制数据(短期)
ZGC 回收流程
阶段 1:初始标记(Concurrent Mark 开始)—— STW,< 1ms
- 标记 GC Roots 直接引用的对象
阶段 2:并发标记(Concurrent Mark)—— 并发
- 遍历对象图,标记所有存活对象
- 使用染色指针的 Marked0/Marked1 位交替标记
- 读屏障处理并发引用变化
阶段 3:并发预备重定位(Concurrent Prepare for Relocate)—— 并发
- 选择需要整理的 Region
- 构建转发表(Forwarding Table)
- 记录旧地址 → 新地址的映射
阶段 4:并发重定位(Concurrent Relocate)—— 并发
- 将存活对象复制到新的 Region
- 更新转发表(旧地址 → 新地址)
- 读屏障自动转发访问(对象转移中仍可正常访问)
- 这是 ZGC 的核心:并发转移,无停顿
阶段 5:并发重映射(Concurrent Remap)—— 并发
- 更新所有指向旧地址的引用
- 与下一次标记阶段合并(优化,避免重复扫描)
- 读屏障逐步修正未更新的引用
时间线
分代 ZGC(Generational ZGC,JDK 21+)
背景
- 非分代 ZGC 每次回收扫描整个堆,大堆 CPU 开销高
- 无法利用"弱分代假说"(大部分对象朝生夕死)
- 内存碎片问题仍存在
分代设计
- 新生代:Eden + Survivor,复制算法,回收频率高
- 老年代:标记-整理算法,回收频率低
- 双标记位循环:新生代和老年代使用不同的 M0/M1 位
- 跨代引用:卡表(Card Table)记录老年代对新生代的引用
- 新生代回收时只需扫描脏卡,无需扫描整个老年代
- JDK 24 移除非分代模式,分代 ZGC 成为唯一模式
性能提升
- 吞吐量提升 20~50%(核心提升)
- CPU 使用率降低 30%
- 内存碎片显著减少
- 最大停顿时间 < 5ms(亚毫秒级)
- 大堆场景 Full GC 概率大幅降低
ZGC 参数
| 参数 | 说明 |
|---|---|
-XX:+UseZGC | 开启 ZGC |
-XX:ZCollectionInterval=<n> | 强制 GC 间隔(秒) |
-XX:ZAllocationSpikeTolerance=<n> | 分配速率容忍度(默认 2) |
-XX:+UseLargePages | 启用大页支持(强烈推荐) |
七、Shenandoah(低延迟收集器,OpenJDK 特性)
适用场景
低延迟要求、Red Hat / OpenJDK 环境、内存受限容器
参数
-XX:+UseShenandoahGC(OpenJDK 12+,JDK 25 分代模式正式化)
Shenandoah 核心设计
1. Brooks Pointer(转发指针)
- 每个对象头额外存储一个转发指针,初始指向自身
- 对象转移时,将原对象的 Brooks 指针指向新对象
- 所有对原对象的访问通过 Brooks 指针自动重定向
- 读屏障检查 Brooks 指针,若已转移则转发
- 转移完成后,后台线程逐步更新所有引用
对象头结构:
┌────────────────────────────────────────┐
│ mark_word │ 标记字 │
│ klass_ptr │ 类指针 │
│ brooks_ptr │ 转发指针(Shenandoah特有)│
└────────────────────────────────────────┘
2. 连接矩阵(Connection Matrix)
- 记录 Region 之间的引用关系
- 优化跨代引用处理
- 减少全堆扫描
3. 读写屏障
- 读屏障:读取对象引用时检查 Brooks 指针
- 写屏障:记录引用变化,更新卡表
- 与 ZGC 对比:ZGC 只有读屏障,Shenandoah 有读写屏障
Shenandoah 回收流程
阶段 1:初始标记(Init Mark)—— STW,< 1ms
- 标记 GC Roots 直接引用
阶段 2:并发标记(Concurrent Marking)—— 并发
- 遍历对象图,标记存活对象
阶段 3:最终标记(Final Mark)—— STW,< 1ms
- 处理并发标记期间的引用变化
阶段 4:并发清理(Concurrent Cleanup)—— 并发
- 清理垃圾 Region
阶段 5:并发转移(Concurrent Evacuation)—— 并发
- 复制存活对象到新的 Region
- 更新 Brooks 指针(旧对象 → 新对象)
- 读屏障自动转发访问
阶段 6:并发更新引用(Concurrent Update References)—— 并发
- 更新所有指向旧地址的引用
分代 Shenandoah(JDK 24+ 实验性,JDK 25 正式)
- 分代模式吞吐量提升 ~30%(相比单代)
- 停顿时间 1~5ms(典型),偶尔 ~10ms
- 支持压缩指针(Compressed Oops),内存开销比 ZGC 低
- JDK 25 不再需要 -XX:+UnlockExperimentalVMOptions
Shenandoah 参数
| 参数 | 说明 |
|---|---|
-XX:+UseShenandoahGC | 开启 Shenandoah |
-XX:ShenandoahGCMode=generational | 分代模式(JDK 25 默认) |
-XX:ShenandoahGCHeuristics=<mode> | 启发式模式(adaptive/static/compact) |
-XX:+ShenandoahPacing | pacing 控制分配速率 |
八、GC 收集器全维度对比表(2026 JDK 25)
| 维度 | Serial | Parallel | CMS | G1 | ZGC(分代) | Shenandoah(分代) |
|---|---|---|---|---|---|---|
| JDK 支持 | 所有 | 所有 | 所有 | 7+ | 11+ | 12+ |
| 默认状态 | 淘汰 | JDK8默认 | 已废弃 | JDK9+默认 | 可选 | 可选(OpenJDK) |
| 回收区域 | 年轻/老 | 年轻/老 | 老年代 | 整堆 | 整堆 | 整堆 |
| 核心算法 | 复制/整理 | 复制/整理 | 标记-清除 | Region混合 | 染色指针+读屏障 | Brooks指针 |
| 工作模式 | 串行STW | 并行STW | 并发 | 并发+STW | 全并发 | 全并发 |
| 典型停顿 | <100ms | <100ms | <1s | 20-200ms | 0.1-0.5ms | 1-5ms |
| 最坏停顿 | 秒级 | 秒级 | 秒级 | 500ms | <1ms | ~10ms |
| 吞吐量(vs G1) | 低 | 最高 | 中 | 基准 | -5~15% | -5~10% |
| 内存开销(vs G1) | 低 | 低 | 中 | 基准 | +15~30% | +10~20% |
| CPU开销(vs G1) | 低 | 低 | 中 | 基准 | +5~10% | +5~15% |
| 压缩指针 | 支持 | 支持 | 支持 | 支持 | 不支持 | 支持 |
| 最佳堆范围 | <2GB | 任意 | 4-16GB | 4-32GB | 32GB-多TB | 8-64GB |
| 内存碎片 | 无 | 无 | 严重 | 无 | 无 | 无 |
| 适用场景 | 客户端 | 批处理 | 低停顿(JDK8) | 通用 | 超低延迟 | 低延迟容器 |
| 2026 推荐 | ❌ | ❌ | ❌ | ✅ 默认 | ✅ 延迟敏感 | ✅ OpenJDK |
九、GC 收集器选择决策树(2026)
JDK 版本建议
| JDK 版本 | 推荐收集器 |
|---|---|
| JDK 8 | G1(-XX:+UseG1GC)或 CMS(遗留系统) |
| JDK 11~16 | G1(默认),可选 ZGC(实验性) |
| JDK 17~21 | G1(默认),ZGC(正式),Shenandoah(实验性) |
| JDK 22+ | G1(默认),分代 ZGC(唯一模式),分代 Shenandoah(正式) |
关键原则
- 先测后选:用实际负载测试不同收集器
- 监控指标:GC 停顿时间、吞吐量、内存碎片、CPU 使用率
- 渐进升级:JDK 8 → JDK 17/21 LTS,逐步验证
- 避免过度优化:G1 已满足大多数场景
十、关键概念深度解析
1. STW(Stop The World)
- GC 期间暂停所有应用线程
- 所有收集器都有 STW 阶段,只是长短不同
- Serial/Parallel:全程 STW
- CMS:初始标记 + 重新标记 STW
- G1:初始标记 + 重新标记 + 混合回收 STW
- ZGC/Shenandoah:仅初始标记 STW(< 1ms)
2. 吞吐量 vs 低延迟
| 指标 | 定义 | 追求 | 代表 |
|---|---|---|---|
| 吞吐量 | 用户代码运行时间 / 总时间 | 单位时间内完成更多工作 | Parallel Scavenge |
| 低延迟 | 单次 GC 停顿时间 | 响应速度,用户体验 | ZGC、Shenandoah |
3. 内存碎片
| 算法 | 碎片情况 | 代表 |
|---|---|---|
| 标记-清除 | 产生碎片 | CMS |
| 标记-整理 | 无碎片 | Serial Old、Parallel Old、G1 |
| 复制 | 无碎片 | Serial、ParNew、Parallel Scavenge |
碎片导致大对象分配失败,触发 Full GC。
4. 写屏障(Write Barrier)vs 读屏障(Load Barrier)
| 屏障类型 | 触发时机 | 作用 | 代表 |
|---|---|---|---|
| 写屏障 | 对象引用写入时 | 记录引用变化 | CMS(增量更新)、G1(SATB)、Shenandoah |
| 读屏障 | 读取对象引用时 | 检查/转发对象状态 | ZGC(染色指针)、Shenandoah(Brooks Pointer) |
屏障有性能开销,但实现并发 GC 的关键。
5. 三色标记算法
| 颜色 | 含义 | 状态 |
|---|---|---|
| 白色 | 未访问 | 垃圾对象 |
| 灰色 | 已访问,引用未完全扫描 | 待处理 |
| 黑色 | 已访问,引用完全扫描 | 存活对象 |
并发标记问题:黑→白引用被切断,白对象被误回收
- CMS 解决:增量更新(关注新增引用)
- G1/ZGC 解决:SATB(关注旧值,保守但安全)
6. 弱分代假说(Weak Generational Hypothesis)
- 大部分对象朝生夕死(在年轻代被回收)
- 熬过多次 GC 的对象很难死亡(进入老年代)
- 分代设计的理论基础:年轻代频繁回收,老年代低频回收
