Java随笔-GC收集器工作机制

📅 2026/7/8 2:10:08 👁️ 阅读次数 📝 编程学习
Java随笔-GC收集器工作机制

一、GC 收集器分类总览

按回收区域分类

  • 年轻代收集器:Serial、ParNew、Parallel Scavenge
  • 老年代收集器:Serial Old、Parallel Old、CMS
  • 整堆收集器:G1、ZGC、Shenandoah

按工作模式分类

模式特点代表
串行(Serial)单线程,全程 STWSerial
并行(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:+ShenandoahPacingpacing 控制分配速率

八、GC 收集器全维度对比表(2026 JDK 25)

维度SerialParallelCMSG1ZGC(分代)Shenandoah(分代)
JDK 支持所有所有所有7+11+12+
默认状态淘汰JDK8默认已废弃JDK9+默认可选可选(OpenJDK)
回收区域年轻/老年轻/老老年代整堆整堆整堆
核心算法复制/整理复制/整理标记-清除Region混合染色指针+读屏障Brooks指针
工作模式串行STW并行STW并发并发+STW全并发全并发
典型停顿<100ms<100ms<1s20-200ms0.1-0.5ms1-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-16GB4-32GB32GB-多TB8-64GB
内存碎片严重
适用场景客户端批处理低停顿(JDK8)通用超低延迟低延迟容器
2026 推荐✅ 默认✅ 延迟敏感✅ OpenJDK

九、GC 收集器选择决策树(2026)


JDK 版本建议

JDK 版本推荐收集器
JDK 8G1(-XX:+UseG1GC)或 CMS(遗留系统)
JDK 11~16G1(默认),可选 ZGC(实验性)
JDK 17~21G1(默认),ZGC(正式),Shenandoah(实验性)
JDK 22+G1(默认),分代 ZGC(唯一模式),分代 Shenandoah(正式)

关键原则

  1. 先测后选:用实际负载测试不同收集器
  2. 监控指标:GC 停顿时间、吞吐量、内存碎片、CPU 使用率
  3. 渐进升级:JDK 8 → JDK 17/21 LTS,逐步验证
  4. 避免过度优化: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 的对象很难死亡(进入老年代)
  • 分代设计的理论基础:年轻代频繁回收,老年代低频回收