🍃 予枫:个人主页
📚 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常》《Java 面试刷题指南》
💻 Debug 这个世界,Return 更好的自己!
引言
作为JVM中经典的并发垃圾收集器,CMS(Concurrent Mark Sweep)凭借“低停顿”的优势,曾是Java后端高并发场景的首选,但它的“并发”特性也带来了不少生产坑。本文从核心流程、优缺点、生产常见问题三个维度,结合面试高频考点,一文吃透CMS,帮你避开踩坑,同时应对面试官追问,建议点赞收藏,反复查阅~
文章目录
- 引言
- 一、CMS垃圾收集器核心概述
- 二、CMS核心工作流程(5个阶段)
-
- 2.1 初始标记(Initial Mark)- 短暂STW
- 2.2 并发标记(Concurrent Mark)- 无STW
- 2.3 并发预清理(Concurrent Preclean)- 无STW
- 2.4 重新标记(Remark)- 短暂STW
- 2.5 并发清除(Concurrent Sweep)- 无STW
- 2.6 补充:重置线程(Concurrent Reset)- 无STW
- 三、CMS垃圾收集器优缺点深度剖析
-
- 3.1 核心优点
- 3.2 核心缺点(生产踩坑重点)
- 四、CMS常见生产问题及解决方案(实战重点)
-
- 4.1 问题1:CMS频繁触发Full GC(老年代内存不足)
-
- 现象
- 原因
- 解决方案
- 4.2 问题2:CMS导致CPU使用率过高
-
- 现象
- 原因
- 解决方案
- 4.3 问题3:内存碎片导致大对象分配失败
-
- 现象
- 原因
- 解决方案
- 4.4 问题4:重新标记阶段停顿时间过长
-
- 现象
- 原因
- 解决方案
- 五、面试官追问环节(实战高频)
-
- 追问1:CMS的并发标记和重新标记有什么区别?为什么重新标记需要STW?
- 追问2:CMS产生的浮动垃圾是什么?如何减少浮动垃圾?
- 追问3:CMS和G1收集器的核心区别是什么?各自的适用场景是什么?
- 六、总结
一、CMS垃圾收集器核心概述
CMS 全称 Concurrent Mark Sweep(并发标记-清除),是一种基于“标记-清除”算法实现的垃圾收集器,核心目标是缩短垃圾收集时的停顿时间,适用于对响应时间要求高的场景(如电商、接口服务),也是JDK1.8及之前高并发系统的主流选择。
它的核心特点是:垃圾收集与用户线程并发执行(大部分阶段),仅在初始标记和重新标记两个阶段会产生短暂停顿,这也是它“低停顿”优势的核心来源。
提示:CMS仅作用于老年代,通常与新生代的ParNew收集器配合使用(ParNew负责新生代回收,CMS负责老年代回收),二者协同完成整个JVM的垃圾回收。
二、CMS核心工作流程(5个阶段)
CMS的垃圾收集过程分为5个阶段,其中3个阶段与用户线程并发执行,2个阶段会产生停顿(STW,Stop The World),具体流程如下:
#mermaid-svg-AP3ZzNXvd5oiKrCV{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AP3ZzNXvd5oiKrCV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AP3ZzNXvd5oiKrCV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AP3ZzNXvd5oiKrCV .error-icon{fill:#552222;}#mermaid-svg-AP3ZzNXvd5oiKrCV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AP3ZzNXvd5oiKrCV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AP3ZzNXvd5oiKrCV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AP3ZzNXvd5oiKrCV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AP3ZzNXvd5oiKrCV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AP3ZzNXvd5oiKrCV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AP3ZzNXvd5oiKrCV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AP3ZzNXvd5oiKrCV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AP3ZzNXvd5oiKrCV .marker.cross{stroke:#333333;}#mermaid-svg-AP3ZzNXvd5oiKrCV svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AP3ZzNXvd5oiKrCV p{margin:0;}#mermaid-svg-AP3ZzNXvd5oiKrCV .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-AP3ZzNXvd5oiKrCV .cluster-label text{fill:#333;}#mermaid-svg-AP3ZzNXvd5oiKrCV .cluster-label span{color:#333;}#mermaid-svg-AP3ZzNXvd5oiKrCV .cluster-label span p{background-color:transparent;}#mermaid-svg-AP3ZzNXvd5oiKrCV .label text,#mermaid-svg-AP3ZzNXvd5oiKrCV span{fill:#333;color:#333;}#mermaid-svg-AP3ZzNXvd5oiKrCV .node rect,#mermaid-svg-AP3ZzNXvd5oiKrCV .node circle,#mermaid-svg-AP3ZzNXvd5oiKrCV .node ellipse,#mermaid-svg-AP3ZzNXvd5oiKrCV .node polygon,#mermaid-svg-AP3ZzNXvd5oiKrCV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AP3ZzNXvd5oiKrCV .rough-node .label text,#mermaid-svg-AP3ZzNXvd5oiKrCV .node .label text,#mermaid-svg-AP3ZzNXvd5oiKrCV .image-shape .label,#mermaid-svg-AP3ZzNXvd5oiKrCV .icon-shape .label{text-anchor:middle;}#mermaid-svg-AP3ZzNXvd5oiKrCV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AP3ZzNXvd5oiKrCV .rough-node .label,#mermaid-svg-AP3ZzNXvd5oiKrCV .node .label,#mermaid-svg-AP3ZzNXvd5oiKrCV .image-shape .label,#mermaid-svg-AP3ZzNXvd5oiKrCV .icon-shape .label{text-align:center;}#mermaid-svg-AP3ZzNXvd5oiKrCV .node.clickable{cursor:pointer;}#mermaid-svg-AP3ZzNXvd5oiKrCV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AP3ZzNXvd5oiKrCV .arrowheadPath{fill:#333333;}#mermaid-svg-AP3ZzNXvd5oiKrCV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AP3ZzNXvd5oiKrCV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AP3ZzNXvd5oiKrCV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AP3ZzNXvd5oiKrCV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AP3ZzNXvd5oiKrCV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AP3ZzNXvd5oiKrCV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AP3ZzNXvd5oiKrCV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AP3ZzNXvd5oiKrCV .cluster text{fill:#333;}#mermaid-svg-AP3ZzNXvd5oiKrCV .cluster span{color:#333;}#mermaid-svg-AP3ZzNXvd5oiKrCV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AP3ZzNXvd5oiKrCV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AP3ZzNXvd5oiKrCV rect.text{fill:none;stroke-width:0;}#mermaid-svg-AP3ZzNXvd5oiKrCV .icon-shape,#mermaid-svg-AP3ZzNXvd5oiKrCV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AP3ZzNXvd5oiKrCV .icon-shape p,#mermaid-svg-AP3ZzNXvd5oiKrCV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AP3ZzNXvd5oiKrCV .icon-shape rect,#mermaid-svg-AP3ZzNXvd5oiKrCV .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AP3ZzNXvd5oiKrCV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AP3ZzNXvd5oiKrCV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AP3ZzNXvd5oiKrCV :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
初始标记(STW)
并发标记(并发)
并发预清理(并发)
重新标记(STW)
并发清除(并发)
重置线程(并发)
2.1 初始标记(Initial Mark)- 短暂STW
- 核心操作:标记GC Roots直接关联的对象(如虚拟机栈引用的对象、方法区静态变量引用的对象),不深入遍历对象引用链。
- 特点:停顿时间极短(通常毫秒级),因为仅标记直接关联对象,不处理后续引用。
- 举例:就像给教室点名,只点班长(GC Roots),不点名班长背后的同学(关联对象),速度极快。
2.2 并发标记(Concurrent Mark)- 无STW
- 核心操作:从初始标记的对象出发,并发遍历整个老年代的对象引用链,标记出所有存活对象。
- 特点:用户线程与垃圾收集线程同时执行,不影响应用响应,但会占用一定CPU资源(可能导致应用吞吐量下降)。
- 注意:此阶段用户线程可能会修改对象引用(如创建新对象、断开引用),导致部分标记结果失效(产生“浮动垃圾”)。
2.3 并发预清理(Concurrent Preclean)- 无STW
- 核心操作:处理并发标记阶段用户线程产生的“浮动垃圾”,并标记出被修改的对象,为后续重新标记做准备,减少重新标记的停顿时间。
- 特点:进一步优化标记效率,避免重新标记阶段工作量过大,依然与用户线程并发执行。
2.4 重新标记(Remark)- 短暂STW
- 核心操作:修正并发标记和预清理阶段因用户线程操作导致的标记偏差,重新扫描所有GC Roots关联的对象及被修改的对象,确保标记结果准确。
- 特点:会产生STW,但停顿时间比初始标记稍长(通常几十毫秒),远短于Serial Old等收集器的停顿时间。
- 优化:JDK1.6后引入“增量式重新标记”,将重新标记的工作拆分成多个小阶段,进一步缩短单次停顿时间。
2.5 并发清除(Concurrent Sweep)- 无STW
- 核心操作:遍历老年代,清除所有未被标记的垃圾对象(死亡对象),释放内存空间。
- 特点:与用户线程并发执行,不产生STW,但清除后会产生内存碎片(标记-清除算法的固有问题)。
2.6 补充:重置线程(Concurrent Reset)- 无STW
- 核心操作:清理CMS收集器的标记状态,重置相关数据结构,为下一次垃圾收集做准备,与用户线程并发执行。
小结:CMS的核心优势的是“并发”,通过将大部分阶段与用户线程并行,最大限度减少STW停顿;但代价是占用CPU资源、产生内存碎片和浮动垃圾。
三、CMS垃圾收集器优缺点深度剖析
3.1 核心优点
3.2 核心缺点(生产踩坑重点)
提示:点赞收藏本文,后续遇到CMS相关的生产问题,直接对照缺点排查,效率翻倍!
四、CMS常见生产问题及解决方案(实战重点)
在实际生产环境中,CMS的缺点很容易引发各类问题,以下是4个最常见的问题,结合实战场景给出解决方案,同时补充面试官高频追问。
4.1 问题1:CMS频繁触发Full GC(老年代内存不足)
现象
应用日志中频繁出现“CMS GC”记录,且伴随“Full GC”,接口响应时间突然变长,甚至出现超时。
原因
解决方案
4.2 问题2:CMS导致CPU使用率过高
现象
服务器CPU使用率持续居高不下(超过80%),其中垃圾收集线程占用大量CPU,应用吞吐量下降,接口卡顿。
原因
CMS并发阶段(并发标记、并发清除)会启动多个垃圾收集线程(默认是CPU核心数的1/4),占用大量CPU资源,尤其在CPU核心数较少的服务器上,与用户线程竞争CPU。
解决方案
4.3 问题3:内存碎片导致大对象分配失败
现象
应用抛出“OutOfMemoryError: Java heap space”异常,但查看堆内存监控,老年代还有大量剩余空间(碎片化严重)。
原因
CMS基于标记-清除算法,多次GC后会产生大量内存碎片,大对象无法找到连续的内存空间分配。
解决方案
4.4 问题4:重新标记阶段停顿时间过长
现象
应用偶尔出现短暂卡顿(几十到几百毫秒),排查日志发现,卡顿发生在CMS重新标记阶段。
原因
重新标记阶段需要扫描所有GC Roots关联的对象及被修改的对象,若老年代对象数量过多、引用链过长,会导致STW停顿时间变长。
解决方案
五、面试官追问环节(实战高频)
结合CMS核心知识点,整理3个面试官最常追问的问题,帮你提前准备,从容应对面试。
追问1:CMS的并发标记和重新标记有什么区别?为什么重新标记需要STW?
答:
追问2:CMS产生的浮动垃圾是什么?如何减少浮动垃圾?
答:
追问3:CMS和G1收集器的核心区别是什么?各自的适用场景是什么?
答:

六、总结
CMS垃圾收集器是JVM中经典的并发收集器,核心优势是低停顿,适合高并发、响应时间敏感的场景,但同时存在CPU消耗高、内存碎片、浮动垃圾等问题,在生产环境中需重点关注参数调优和问题排查。
本文从核心流程、优缺点、生产问题、面试追问四个维度,全面解析CMS,掌握这些知识点,不仅能解决生产中的实际问题,还能从容应对面试官的相关提问。



