CMS与G1
CMS与G1
CMS(Concurrent Mark Sweep)和G1(Garbage First)是Java中两种常见的垃圾回收器,分别适用于不同的应用场景。以下是它们的核心区别和特点:
一、CMS(Concurrent Mark Sweep)
1.1 设计目标
- 最小化停顿时间(Low Pause)
- 允许 GC 线程与用户线程并发执行
1.2 核心算法
- 标记-清除(Mark-Sweep)
- 不做压缩 → 会产生内存碎片
1.3 执行流程(非常高频)
1 | 初始标记(STW) → 并发标记 → 重新标记(STW) → 并发清除 |
详细解释
-
初始标记(STW)
- 只标记 GC Roots 直接关联对象
- 很快
-
并发标记
- 从 Roots 开始遍历
- 与用户线程并发执行
-
重新标记(STW)
- 修正并发期间的变动(关键!)
- 使用:增量更新(Incremental Update)
-
并发清除
- 删除垃圾对象
- 不移动对象
1.4 关键问题
内存碎片
-
因为不压缩
-
可能导致:
- 大对象分配失败
- Full GC 提前发生
Concurrent Mode Failure
典型面试问题
原因:
- 并发清除期间,老年代空间不够用了
结果:
- 退化成 Serial Old(STW,全停顿)
CPU 占用高
- GC线程和用户线程竞争 CPU
1.5 关键调优参数
-
-XX:CMSInitiatingOccupancyFraction- 设置触发 GC 的老年代使用率(如 70%)
-
-XX:+UseCMSInitiatingOccupancyOnly- 禁止自适应
1.6 一句话总结 CMS
“以牺牲吞吐量为代价,换取更短停顿时间,但会产生碎片”
二、G1(Garbage First)
2.1 设计目标
-
可预测的停顿时间(Predictable Pause)
-
兼顾:
- 吞吐量
- 低延迟
2.2 核心设计(区别 CMS 的本质)
不再连续分代,而是:
-
将堆划分为多个 Region
-
每个 Region 可以是:
- Eden / Survivor / Old
2.3 关键思想
⭐ Garbage First
- 优先回收垃圾最多的 Region
- 通过统计(RSet + 预测模型)实现
2.4 执行流程(重点🔥)
1 | 初始标记(STW) |
关键阶段解释
- 初始标记(STW)
- 标记 GC Roots 直接引用
- 并发标记
- 标记整个堆的存活对象
- 最终标记(STW)
- 修正并发期间变化
- 使用:SATB(Snapshot At The Beginning)
- 筛选回收(Evacuation)
- 选择高收益 Region
- 复制存活对象(带压缩效果)
2.5 核心机制(面试重点🔥)
- Region
- 堆被拆成多个小块(1~32MB)
- 避免大块内存管理问题
- RSet(Remembered Set)
解决跨 Region 引用问题
-
记录:
- 哪些 Region 引用了当前 Region
避免全堆扫描(非常关键)
- 停顿预测模型
-
通过历史数据预测:
- 本次 GC 回收多少 Region
-
保证:
- 在
-XX:MaxGCPauseMillis目标内完成
- 在
2.6 优点
- 可控停顿时间 ✅
- 减少碎片(复制+整理) ✅
- 适用于大堆(如 8G+) ✅
2.7 缺点
- 实现复杂
- 内存开销大(RSet)
- 小堆下不如 CMS
2.8 一句话总结 G1
“通过 Region + 优先回收策略,实现可预测停顿时间的垃圾回收器”
三、CMS vs G1(核心对比🔥)
| 维度 | CMS | G1 |
|---|---|---|
| 目标 | 最短停顿 | 可预测停顿 |
| 算法 | 标记-清除 | 标记+复制 |
| 碎片 | 有 ❗ | 无(整理) ✅ |
| 并发 | 高 | 高 |
| 内存结构 | 分代(连续) | Region(离散) |
| Full GC 风险 | 高 | 低 |
| 调优难度 | 高 | 相对简单 |
| 默认地位 | 已淘汰 | 当前主流 ⭐ |
四、面试高频追问点
4.1 CMS 为什么要重新标记?
因为并发标记期间对象引用发生变化
4.2 CMS 和 G1 最大区别?
是否压缩内存 + 是否使用 Region
4.3 G1 为什么能预测停顿时间?
通过:
- Region 回收成本统计
- 历史数据模型
4.4. CMS 为什么会 Full GC?
Concurrent Mode Failure
五、结论
- CMS:低停顿,但有碎片,已逐渐淘汰
- G1:低停顿 + 可预测 + 无碎片,是当前主流