欢迎光临
我们一直在努力

【面试专栏|JVM虚拟机】CMS vs 其他垃圾收集器:核心差异+适用场景

在这里插入图片描述

🍃 予枫:个人主页

📚 个人专栏: 《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 核心优点

  • 低停顿:仅初始标记和重新标记阶段产生短暂STW,其余阶段与用户线程并发执行,适合对响应时间敏感的高并发场景(如接口响应时间要求≤100ms)。
  • 并发执行:垃圾收集不阻断用户线程,避免了传统收集器(如Serial Old)长时间STW导致的应用卡顿。
  • 适配老年代:专门针对老年代设计,与ParNew收集器配合,能很好地应对老年代对象存活时间长、回收频率低的特点。
  • 3.2 核心缺点(生产踩坑重点)

  • CPU资源消耗高:并发标记、并发清除等阶段会占用大量CPU资源,在CPU核心数较少(如2核、4核)的服务器上,会导致应用吞吐量明显下降(比如接口QPS降低)。
  • 产生内存碎片:基于“标记-清除”算法,清除垃圾后会留下大量不连续的内存碎片,导致老年代内存明明有剩余空间,却无法分配给大对象,最终触发Full GC(Serial Old收集器执行,停顿时间极长)。
  • 存在浮动垃圾:并发标记阶段,用户线程可能创建新对象、断开对象引用,这些未被标记的垃圾(浮动垃圾)无法在本次GC中清除,只能等到下一次GC,可能导致老年代内存提前占满,触发频繁GC。
  • 无法处理大对象:由于内存碎片问题,当需要分配大对象(如超过100MB的数组)时,容易出现“内存不足”异常,即使老年代总内存充足。
  • GC停顿不可控:虽然整体停顿短,但重新标记阶段的停顿时间可能因对象数量过多而变长,且并发阶段占用CPU,可能间接导致应用响应变慢。
  • 提示:点赞收藏本文,后续遇到CMS相关的生产问题,直接对照缺点排查,效率翻倍!

    四、CMS常见生产问题及解决方案(实战重点)

    在实际生产环境中,CMS的缺点很容易引发各类问题,以下是4个最常见的问题,结合实战场景给出解决方案,同时补充面试官高频追问。

    4.1 问题1:CMS频繁触发Full GC(老年代内存不足)

    现象

    应用日志中频繁出现“CMS GC”记录,且伴随“Full GC”,接口响应时间突然变长,甚至出现超时。

    原因

  • 老年代内存分配过小,对象晋升速度过快(新生代对象频繁晋升到老年代);
  • 存在内存泄漏(如静态集合持有大量对象引用,未及时释放);
  • 浮动垃圾过多,导致老年代内存提前占满。
  • 解决方案

  • 调整JVM参数:增大老年代内存(-Xms、-Xmx设置更大值,同时调整-XX:CMSInitiatingOccupancyFraction,默认68%,可调整为75%-80%,推迟CMS触发时机);
  • 排查内存泄漏:使用JProfiler、MAT等工具,分析堆内存快照,找到未释放的大对象和内存泄漏点(如静态Map未清理);
  • 优化对象创建:减少大对象创建,避免短生命周期对象被长期引用(如避免将局部对象存入静态集合)。
  • 4.2 问题2:CMS导致CPU使用率过高

    现象

    服务器CPU使用率持续居高不下(超过80%),其中垃圾收集线程占用大量CPU,应用吞吐量下降,接口卡顿。

    原因

    CMS并发阶段(并发标记、并发清除)会启动多个垃圾收集线程(默认是CPU核心数的1/4),占用大量CPU资源,尤其在CPU核心数较少的服务器上,与用户线程竞争CPU。

    解决方案

  • 调整CMS线程数:通过-XX:ParallelCMSThreads参数,减少CMS垃圾收集线程数(如CPU核心数为4,可设置为1或2),降低CPU占用;
  • 升级服务器配置:增加CPU核心数,缓解CPU竞争压力;
  • 优化应用代码:减少频繁创建和销毁对象,降低垃圾产生速度,减少CMS触发频率。
  • 4.3 问题3:内存碎片导致大对象分配失败

    现象

    应用抛出“OutOfMemoryError: Java heap space”异常,但查看堆内存监控,老年代还有大量剩余空间(碎片化严重)。

    原因

    CMS基于标记-清除算法,多次GC后会产生大量内存碎片,大对象无法找到连续的内存空间分配。

    解决方案

  • 开启内存碎片整理:通过-XX:+UseCMSCompactAtFullCollection参数,在CMS执行完Full GC后,进行一次内存碎片整理(会产生STW,停顿时间变长,需权衡);
  • 调整整理频率:通过-XX:CMSFullGCsBeforeCompaction参数,设置多少次Full GC后进行一次碎片整理(默认0,即每次Full GC后都整理);
  • 替换收集器:如果内存碎片问题严重,可替换为G1收集器(基于标记-整理算法,无内存碎片),但需注意G1的参数调优。
  • 4.4 问题4:重新标记阶段停顿时间过长

    现象

    应用偶尔出现短暂卡顿(几十到几百毫秒),排查日志发现,卡顿发生在CMS重新标记阶段。

    原因

    重新标记阶段需要扫描所有GC Roots关联的对象及被修改的对象,若老年代对象数量过多、引用链过长,会导致STW停顿时间变长。

    解决方案

  • 开启增量式重新标记:通过-XX:+CMSIncrementalMode参数,将重新标记的工作拆分成多个小阶段,分散停顿时间;
  • 减少老年代对象数量:优化应用代码,减少长期存活对象的创建,降低重新标记的工作量;
  • 调整JVM参数:通过-XX:CMSScavengeBeforeRemark参数,在重新标记前先执行一次新生代GC,减少老年代对象的引用链长度。
  • 五、面试官追问环节(实战高频)

    结合CMS核心知识点,整理3个面试官最常追问的问题,帮你提前准备,从容应对面试。

    追问1:CMS的并发标记和重新标记有什么区别?为什么重新标记需要STW?

    答:

  • 并发标记:与用户线程并发执行,遍历GC Roots关联的对象,标记存活对象,但可能因用户线程修改引用,导致标记偏差;
  • 重新标记:需要STW,因为要修正并发标记阶段的标记偏差,重新扫描所有GC Roots和被修改的对象,确保标记结果准确——如果不STW,用户线程继续修改引用,标记结果会一直不准确,导致垃圾回收遗漏或误删存活对象。
  • 追问2:CMS产生的浮动垃圾是什么?如何减少浮动垃圾?

    答:

  • 浮动垃圾:并发标记阶段,用户线程创建的新对象、断开引用的对象,这些对象未被本次GC标记,无法在本次GC中清除,只能等到下一次GC,称为浮动垃圾;
  • 减少方法:① 推迟CMS触发时机(调整-XX:CMSInitiatingOccupancyFraction),给浮动垃圾更多时间被回收;② 减少短生命周期对象的创建,降低并发阶段的对象修改频率;③ 增大老年代内存,避免浮动垃圾快速占满老年代。
  • 追问3:CMS和G1收集器的核心区别是什么?各自的适用场景是什么?

    答:

  • 核心区别:① 算法不同:CMS基于标记-清除(有内存碎片),G1基于标记-整理(无内存碎片);② 回收方式不同:CMS仅回收老年代,需配合ParNew,G1可同时回收新生代和老年代;③ 停顿控制不同:CMS无法精确控制停顿时间,G1可通过-XX:MaxGCPauseMillis设置最大停顿时间;
  • 适用场景:① CMS:适用于JDK1.8及之前,对响应时间敏感、CPU资源充足、不介意内存碎片的高并发场景;② G1:适用于JDK1.7及之后,内存较大(如8G以上)、需要精确控制停顿时间、避免内存碎片的场景。 在这里插入图片描述
  • 六、总结

    CMS垃圾收集器是JVM中经典的并发收集器,核心优势是低停顿,适合高并发、响应时间敏感的场景,但同时存在CPU消耗高、内存碎片、浮动垃圾等问题,在生产环境中需重点关注参数调优和问题排查。

    本文从核心流程、优缺点、生产问题、面试追问四个维度,全面解析CMS,掌握这些知识点,不仅能解决生产中的实际问题,还能从容应对面试官的相关提问。

    赞(0)
    未经允许不得转载:171主机测评 » 【面试专栏|JVM虚拟机】CMS vs 其他垃圾收集器:核心差异+适用场景
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址