本文的 原始地址 ,传送门
尼恩说在前面
在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:
听说你是高手,说说,你的CMS怎么调优?
说说,CMS 垃圾回收器的底层原理?
说说,CMS 的浮动垃圾,是怎么处理的?
说说,CMS 垃圾回收器的调优过程?
最近有小伙伴在面试 美团,又遇到了相关的面试题。小伙伴懵了,因为没有遇到过,所以支支吾吾的说了几句,面试官不满意,面试挂了。
所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V171版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,回复:领电子书
另外,此文的内容,作为0第10章,收入尼恩的《JVM 调优圣经》PDF。
第10章:cms 底层原理和调优实战
《cms 底层原理和调优实战》内容正在写作中,本月底发布。
尼恩希望通过 JVM调优圣经一个PDF,帮助大家一举成为 JVM调优 小王子。
实现通过JVM调优 的超级技能,去毒打面试官。
完整的PDF还在写作中,晚些时候进行发布。
说在最后:有问题找老架构取经
《JVM 调优圣经》PDF 第11章:
美团面试:G1 垃圾回收 底层原理是什么?说说你的调优过程?
接下来,咱们言归正传,开始讲 cms
面试背景
CMS(Concurrent Mark Sweep)作为老年代垃圾回收器,以低延迟为核心目标,适用于对响应时间敏感的系统。尽管G1、ZGC等新回收器逐渐普及,但CMS仍广泛存在于传统企业级应用中。面试官提问CMS底层原理及调优,主要考察以下能力:
(1) 对并发垃圾回收机制的理解(如如何减少STW时间);
(2) 实际调优经验(参数调整、问题定位能力);
(3) 对JVM内存模型与GC算法的综合掌握程度(如卡表机制、碎片处理)。
回答核心要点
CMS底层原理
四阶段工作流程
- **初始标记(Initial Mark)**:STW阶段,标记GC Roots直接关联的对象;
- **并发标记(Concurrent Mark)**:与用户线程并发执行,遍历老年代对象引用链;
- **重新标记(Remark)**:STW阶段,修正并发标记期间变动的引用关系(通过增量更新或原始快照算法);
- **并发清除(Concurrent Sweep)**:删除无引用对象,回收内存空间。
关键技术机制
- **卡表(Card Table)**:将老年代划分为512字节的卡片,记录跨代引用,避免YGC时扫描整个老年代;
- 增量并发:通过交替执行GC线程与用户线程减少STW时间,但可能引发并发失败(Concurrent Mode Failure)。
CMS调优策略
核心参数调整
-XX:CMSInitiatingOccupancyFraction=70:老年代内存占用达70%时触发CMS回收,避免Full GC;-XX:+UseCMSCompactAtFullCollection:Full GC时压缩内存碎片(默认关闭,需权衡性能);-XX:+CMSParallelRemarkEnabled:启用并行重新标记,减少Remark阶段耗时。
典型问题场景调优
- 频繁Concurrent Mode Failure
表现:触发Serial Old回收器导致长STW。
解决:增大老年代空间或降低CMSInitiatingOccupancyFraction阈值。 - 内存碎片严重
表现:老年代剩余空间足够但无法分配大对象(晋升失败)。
解决:启用UseCMSCompactAtFullCollection或周期性Full GC。 - Remark阶段耗时高
解决:通过CMSScavengeBeforeRemark在Remark前触发YGC,减少跨代引用跟踪负担。
调优优先级策略
- 先监控后调优:使用
jstat -gcutil观察GC频率/耗时,jmap分析内存分布; - 扩容优先:若硬件成本允许,优先增加内存而非复杂调优(符合生产环境常见做法)。
回答策略
结构化表达:按“原理→问题→解决方案”逻辑展开,如先说明CMS阶段,再针对各阶段常见问题给出调优方法;
结合场景举例:如描述某次线上服务因碎片问题导致Full GC耗时增加,通过调整CMSFullGCsBeforeCompaction解决;
强调权衡意识:如低延迟与吞吐量的取舍、碎片压缩与暂停时间的平衡;
关联新技术:对比CMS与G1/ZGC的优劣,体现技术视野(如CMS适用于中小堆,ZGC适合超大堆)。
避坑指南
避免过度调优:强调JVM默认参数在多数场景已足够可靠,调优需以监控数据为依据;
勿混淆概念:区分卡表(Card Table)与记忆集(Remembered Set),前者用于跨代引用,后者用于分代收集器的跨区域引用。
CMS解决了什么问题?
CMS 的核心价值在于 以并发回收机制实现毫秒级停顿,尤其适合中小规模堆内存下对延迟敏感的场景(如实时服务)。但其内存碎片和 CPU 资源占用问题需通过参数调优规避,且在超大堆(≥8GB)或长期运行场景中,G1 或 ZGC 可能是更优选择。
CMS(Concurrent Mark Sweep)垃圾回收器主要解决以下问题:
-
减少垃圾回收停顿时间
-
高吞吐量与低延迟的平衡
-
大堆内存管理
-
解决内存碎片问题
-
并发标记和清除
-
处理对象引用变化
什么是CMS?
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。
在启动 JVM 的参数加上-XX:+UseConcMarkSweepGC来指定使用 CMS 垃圾回收器。
CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
CMS(Concurrent Mark Sweep)是基于标记-清除算法的老年代垃圾收集器,设计目标为最小化停顿时间。其核心思想是通过并发标记与清理减少STW(Stop The World)时长,适用于对延迟敏感的应用场景(如Web服务)。
9款 垃圾回收器 的JDK版本
了解下 HotSpot虚拟机中 9款 垃圾回收器 的发布时间及其对应的 JDK版本,如下图:

| 版本 | 发布时间 | 默认收集器 | 事件 |
|---|---|---|---|
| jdk1.3 | 2000-05-08 | serial | |
| jdk1.4 | 2004-02-06 | ParNew | |
| jdk1.5/5.0 | 2004-09-30 | Parallel Scavenge/serial | CMS登场 |
| jdk1.6/6.0 | 2006-12-11 | Parallel Scavenge/Parallel Old | |
| dk1.7/7.0 | 2011-07-28 | Parallel Scavenge/Parallel Old | G1登场 |
| jdk1.8/8.0 | 2014-03-18 | Parallel Scavenge/Parallel Old | |
| jdk1.9/9.0 | 2014-09-8 | G1 | CMS废弃 |
| jdk10 | 2018-03-21 | G1 | |
| jdk11 | 2018-09-25 | G1 | ZGC登场 |
| jdk12 | 2019-3 | G1 | Shenandoah |
| jdk13 | 2019-9 | G1 | |
| jdk14 | 2020-3 | G1 | CMS移除 |
| jdk15 | 2020-9-15 | G1 | ZGC、Shenandoah转正 |
| jdk16 | 2021-3-16 | G1 | |
| jdk17 | 2021-09-14 | G1 | |
| jdk21 | 2022-3-22 | G1 | ZGC分代 |
| jdk23 | 2022-9-22 | ZGC |
JDK 1.4.1 时 ,CMS 垃圾收集器被引入,在2020年3月,JDK 14 版本,CMS从 JDK中移除。
G1垃圾收集器 在 JDK 7 时引入,在 JDK 9 时G1取代 CMS 成为了默认的垃圾收集器。
就目前来说,JVM 的垃圾收集器主要分为两大类:分代收集器和分区收集器,分代收集器的代表是 CMS,分区收集器的代表是 G1 和 ZGC,下面我们来看看这两大类的垃圾收集器。
如下图:

说明:分代垃圾收集器中,新生代有 Serial、ParNew、Parallel Scavenge,老年代包括 CMS、MSC、Parallel old,收集器之间的连线说明两者可以搭配使用。
CMS的核心机制是什么?
(1) 并发标记与清理:
通过多线程与用户线程并发执行标记和清理操作,仅初始标记和重新标记阶段需短暂STW
(2) 三色标记法:
基于黑(已标记且存活)、灰(标记中)、白(未标记或垃圾)的对象状态跟踪,结合写屏障(Write Barrier)记录并发阶段的对象引用变化,防止漏标
(3) 内存碎片处理:
标记- 清除算法不压缩内存 ,长期运行后可能产生碎片,依赖Full GC(Serial Old)或参数触发碎片整理**
CMS的特点:
- 并发收集:GC 线程与用户线程并发执行
- 低停顿:追求最短回收停顿时间
- 标记-清除算法:会产生内存碎片
- 分代收集:与 ParNew 配合使用(年轻代用 ParNew,老年代用 CMS)
CMS 几个基本数据结构
结构1:卡表
为什么需要卡表?
CMS垃圾回收器需要卡表的核心原因在于优化跨代引用扫描效率,解决跨代引用扫描问题的效率问题:
1 跨代引用带来的性能瓶颈
在YGC(年轻代垃圾回收)时,老年代对象可能引用年轻代对象。
若直接扫描整个老年代寻找 跨代引用,时间和资源消耗将无法接受。
2 并发标记阶段,记录引用变化
CMS 并发标记阶段中,老年代引用变化会通过写屏障标记到卡表,核心目的是在重新标记阶段高效修正并发期间遗漏的存活对象标记。
卡表在此场景下不仅用于跨代引用跟踪,还直接服务于老年代内部引用变更的记录与处理
3 卡表的核心作用
卡表通过**将老年代划分为固定大小的卡片(Card,通常512字节)**,并记录卡片是否被修改(即脏卡标记)。
YGC时仅需扫描标记为脏的卡片,而非全量扫描老年代,大幅降低扫描范围。
卡表 在 YGC 的时候, 贡献很大。
什么是卡表?
对于分代垃圾回收器,势必存在一个跨代引用的问题,
而卡表就是最常用的一种跨代引用 记录结构 ,它是一个字节数组,用于记录堆内存的映射关系,下面是 HotSpot虚拟机默认的卡表标记逻辑:
// >> 9 代表右移 9位,即 2^9 = 512 字节
CARD_TABLE[this address >> 9] = 0;
每个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块叫做“卡页(Card Page)”。
因为卡页代表的是一个区域,所以可能存在很多对象,只要有一个对象存在跨代引用,就把数组的值设为1,称该元素“变脏(Dirty)”,该卡页叫“脏页(Dirty Page)”,
如下: 1 2 当垃圾回收时,只要筛选卡表中有变脏的元素,即数组值为 1,就能判断出其对应的内存区域存在对象跨代引用,卡表和卡页的关系如下图:

在CMS(Concurrent Mark-Sweep)垃圾收集器中,卡表(Card Table)的主要作用是记录老年代对象对年轻代对象的引用,以加速年轻代垃圾收集(YGC)的过程。在YGC时,卡表帮助快速定位哪些老年代对象可能引用了年轻代对象,从而避免扫描整个老年代,提升效率。
在Full GC(全局垃圾收集)的场景中,卡表的贡献相对有限,因为Full GC会扫描整个堆内存,包括年轻代和老年代,确保所有不可达对象都被回收。具体来说:
1 Full GC的全面扫描:
Full GC会遍历整个堆内存,标记所有存活对象,并回收所有不可达对象。由于Full GC已经进行了全局扫描,卡表的作用被弱化,因为不再需要依赖卡表来定位跨代引用。
2 卡表的维护:
尽管在Full GC中卡表的作用不明显,但卡表仍然会被维护,以确保在后续的YGC中能够正常工作。Full GC会清理卡表,确保其内容与堆内存的实际情况一致。
3 性能影响:
在Full GC期间,卡表的维护可能会带来一定的开销,但这种开销通常较小,因为Full GC的主要性能瓶颈在于全局扫描和对象回收。
总结来说,在Full GC的场景中,卡表的贡献较小,因为Full GC已经通过全局扫描处理了所有对象引用。卡表的主要作用仍然体现在YGC中,帮助加速跨代引用的处理。
结构2:写屏障
在 HotSpot虚拟机中,写屏障本质上是引用字段被赋值这个事件的一个环绕切面(Around AOP),即一个引用字段被赋值的前后可以为程序提供额外的动作(比如更新卡表)
CMS垃圾回收器原理、调优及问题分析

最低0.47元/天 解锁文章
174万+





