CMS:
Concurrent Mark Sweep,并发标记清除收集器
STW 初始标记 → 并发标记 → 并发预清理 → STW 重新标记 → 并发清除 → 重置
详解:
bash
1. 核心设计目的:
1.1 降低GC停顿时间(低延迟优先)
Parallel GC是 "stop-the-world 长时间停顿换取高吞吐量",CMS反过来:
尽可能让GC线程和业务用户线程并发执行,减少STW暂停时长,对接口响应、交易系统更友好。
1.2 专门针对老年代回收
CMS 只负责老年代,新生代依然使用 ParNew 并行收集器,组合为:ParNew + CMS。
2. CMS 底层执行原理(7 个阶段,重点区分 STW / 并发)
2.1 关键:只有 初始标记、重新标记 两个阶段会STW,其余和业务线程并发跑。
2.2 阶段 1:初始标记(Initial Mark)------STW
2.2.1暂停所有业务线程
2.2.2只扫描GC Roots 直接关联的对象(第一层可达对象),速度极快,STW 毫秒级
2.2.3标记出这些根对象,作为后续遍历起点
2.3 阶段 2:并发标记(Concurrent Mark)------与用户线程并发执行
2.3.1GC 后台线程遍历整个老年代对象图,递归标记所有存活对象
2.3.2业务代码正常运行,期间会产生浮动垃圾(标记过程中新产生、
又变成垃圾的对象,本轮无法回收)
2.3.3这一步耗时最长,但不停业务
2.4 阶段 3:并发预清理(Concurrent Preclean)------ 并发
2.4.1自动执行,目的:清理并发标记期间被修改的对象(卡片表 Card Table 记录跨代引用)
2.4.2自动执行,目的:尝试减少下一阶段重新标记的工作量,纯后台并发,无 STW
2.5 阶段 4:可取消的并发预清理(Concurrent Abortable Preclean)------ 并发
2.5.1可通过参数控制时长,主要应对在重新标记前尽量消化掉脏卡,
降低 remark 停顿压力;内存波动大时可提前退出
2.6 阶段 5:重新标记(Remark)------第二次 STW,CMS 最长停顿点
必须暂停所有线程,修正前面并发标记期间因为业务线程运行导致的引用变动:
2.6.1新分配对象
2.6.2对象引用被修改,这是 CMS 单次最长 STW,
所以调优会加 `-XX:+CMSScavengeBeforeRemark`,先做一次 YGC 再 remark,减少扫描量。
2.7 并发清除(Concurrent Sweep)------ 并发
GC 线程后台遍历堆,直接清除未被标记的死亡对象,不压缩、不整理内存,
标记 - 清除算法,不会做内存碎片整理。
2.8 阶段 7:并发重置(Concurrent Reset)------ 并发
重置 CMS 内部标记状态、指针、标记位图,为下一次 GC 周期做准备。
3. CMS 优点
3.1 低延迟,STW 时间非常短
两次 STW 都很短,绝大多数工作并发执行,对 RT 敏感业务极其友好,这是最大优势。
3.2 多核 CPU 下表现优秀
ParNew 多线程回收新生代,CMS 标记阶段多线程并发,利用多核算力
3.3 JDK8 长期稳定可用,运维工具成熟
大量老项目存量使用,GC 日志、故障排查方案完备。
4. CMS 致命缺点(为什么 JDK9 删除、新项目不再推荐)
4.1 标记 - 清除算法,产生大量内存碎片(最严重问题)
标记 - 清除算法,产生大量内存碎片(最严重问题)
内存千疮百孔,大对象无法分配,触发 Concurrent Mode Failure(并发模式失败)
一旦 CMF 发生,CMS 直接降级为Serial Old 单线程 FullGC,超长 STW,服务卡顿雪崩
解决方案(治标不治本):
-XX:+UseCMSCompactAtFullCollection
# 每 5 次 FullGC 做一次碎片压缩,但压缩本身是长时间 STW,违背低延迟初衷。
-XX:CMSFullGCsBeforeCompaction=5
4.2 浮动垃圾问题
并发标记、并发清理阶段业务线程还在创建对象,这些新垃圾本轮回收不到,只能留到下一次 GC。
为了容纳浮动垃圾,必须提前触发 CMS,不能等老年代占满再回收,依赖参数:
# 老年代占用 70% 就启动并发标记,预留 30% 空间放浮动垃圾;堆越大,浮动垃圾风险越高。
-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSInitiatingOccupancyOnly
4.3 对 CPU 资源消耗较高
多个并发后台线程持续运行,CPU 使用率会比 G1、Parallel 更高,小核数机器不划算。
4.4 无法处理超大堆内存
一般建议堆≤8G,超过 8G 极易并发模式失败、频繁 FullGC;16G + 堆直接 G1。
4.5 元空间类卸载能力偏弱
需要手动开启:`-XX:+CMSClassUnloadingEnabled`,否则动态类加载容易元空间溢出。
4.6 JDK 官方已废弃
DK9 开始移除 CMS,官方推荐 G1;后续 ZGC/Shenandoah 进一步碾压其低延迟能力。
4.7 吞吐量不如 Parallel GC
因为后台 GC 线程抢占 CPU,单位时间处理请求量略低于吞吐量收集