CMS 和 G1 垃圾回收器的区别是什么?
结论先行:CMS 是老年代专用的并发标记-清除收集器,配合 ParNew 处理新生代,追求最短停顿但不压缩、有碎片;G1 是整堆管理的收集器,把堆切成大小相等的 Region,按「垃圾最多优先」回收,可设定停顿目标且自带整理。一句话:CMS 用碎片换停顿,G1 用可预测停顿换掉碎片问题,并因此成为 JDK 9 之后的默认收集器。
一、核心差异
| 维度 | CMS | G1 |
|---|---|---|
| 管理范围 | 只管老年代,新生代交给 ParNew | 整堆(新生代、老年代都是 Region 逻辑划分) |
| 内存布局 | 连续的 Eden / Survivor / Old | 等大 Region(1~32MB),逻辑分代,物理不连续 |
| 标记算法 | 三色标记 + 增量更新(写后屏障) | 三色标记 + SATB(写前屏障) |
| 压缩整理 | 默认不压缩,靠 CMSFullGCsBeforeCompaction 定期整理 | 回收即复制整理,天然无碎片 |
| 停顿控制 | 无法设定目标,靠调阈值 | -XX:MaxGCPauseMillis 可设目标 |
| 大对象 | 直接进老年代,易碎片化 | Humongous Region(≥ Region 一半) |
| 适用堆 | 4~8G | 4~32G(更大需 ZGC) |
| 状态 | JDK 9 废弃,JDK 14 移除 | JDK 9+ 默认 |
二、流程对比
CMS(老年代):
初始标记(STW) → 并发标记 → 重新标记(STW) → 并发清除 → 并发重置
问题:清除不压缩 → 碎片 → 大对象分配失败 → concurrent mode failure → 单线程 Full GC
G1:
Young GC(STW, 复制) ──→ 并发标记周期(初始标记借 Young GC / 并发标记 / 重新标记 / 清理)
└─→ Mixed GC(多轮,回收 Young + 高垃圾占比 Old Region)
└─→ 转移失败 → Full GC(单线程,兜底)
三、参数对照
# CMS:启动阈值 + 碎片治理 + 重新标记前先做一次 Young GC
java -XX:+UseConcMarkSweepGC -XX:+UseParNewGC \
-XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly \
-XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection \
-XX:+CMSScavengeBeforeRemark -jar legacy.jar
# G1:停顿目标 + Region 大小 + 并发标记启动阈值
java -XX:+UseG1GC -XX:MaxGCPauseMillis=150 \
-XX:G1HeapRegionSize=8m -XX:InitiatingHeapOccupancyPercent=45 \
-XX:G1NewSizePercent=20 -XX:G1MaxNewSizePercent=40 \
-XX:ConcGCThreads=4 -jar modern.jar
四、G1 如何在 Region 间排队
G1 不按「老年代/新生代」的顺序回收,而是按收益排队:
| 依据 | 含义 | 影响 |
|---|---|---|
| 垃圾占比 | Region 中可回收对象的比例 | 占比越高越优先,这就是「Garbage First」的由来 |
| GC 效率标记 | 记在 Region 的标记位图里 | 混合回收只挑标记过的候选集 |
| 预测模型 | 用历史数据估算回收该 Region 的耗时 | 保证总停顿不超过 MaxGCPauseMillis |
| 记忆集成本 | 跨 Region 引用越多,扫描越慢 | 引用密集的 Region 会被降低优先级 |
回收顺序由「垃圾占比高 + 预测耗时短」共同决定,所以 G1 的停顿是「目标值 + 预测误差」,不是硬保证。
五、什么时候还该用 CMS
| 场景 | 结论 |
|---|---|
| JDK 8 且堆 < 4G、停顿敏感 | 可用,但必须配碎片治理与阈值 |
| 堆 4~8G、对象分配速率平稳 | G1 更稳,CMS 的并发模式失败风险更高 |
| 大量大对象(如大数组、缓存块) | 避免 CMS,碎片会直接导致 Full GC |
| 新项目 | 不用 CMS,JDK 14 起已移除 |
六、常见追问
| 追问 | 回答要点 |
|---|---|
| 为什么 CMS 被废弃 | 碎片不可控、并发模式失败代价高、维护成本大,G1 在小中堆上已全面取代 |
| G1 就一定没有长停顿吗 | 有,转移失败与 Humongous 分配失败都会退化为 Full GC |
| 为什么 G1 用 SATB 而不是增量更新 | SATB 只需在重新标记阶段处理删除的引用,且新对象直接视为存活,实现更简单、remark 更短 |
| G1 的 Region 越大越好吗 | 不是,Region 太大导致单次回收粒度粗、Humongous 阈值变高;一般 1~32MB,按堆大小自动推导 |
| ZGC 与 G1 的关系 | ZGC 是并发转移,停顿 < 1ms 且与堆大小基本无关;G1 仍是分代复制,停顿随堆增长 |
小结
CMS 是「并发清除、不管碎片」的旧方案,G1 是「Region 化、可预测停顿、回收即整理」的新方案。答这一题的关键是讲清碎片与停顿控制这两条主线,而不是罗列参数。