予枫个人主页 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常》《Java 面试刷题指南》 Debug 这个世界Return 更好的自己引言作为JVM中经典的并发垃圾收集器CMSConcurrent Mark Sweep凭借“低停顿”的优势曾是Java后端高并发场景的首选但它的“并发”特性也带来了不少生产坑。本文从核心流程、优缺点、生产常见问题三个维度结合面试高频考点一文吃透CMS帮你避开踩坑同时应对面试官追问建议点赞收藏反复查阅文章目录引言一、CMS垃圾收集器核心概述二、CMS核心工作流程5个阶段2.1 初始标记Initial Mark- 短暂STW2.2 并发标记Concurrent Mark- 无STW2.3 并发预清理Concurrent Preclean- 无STW2.4 重新标记Remark- 短暂STW2.5 并发清除Concurrent Sweep- 无STW2.6 补充重置线程Concurrent Reset- 无STW三、CMS垃圾收集器优缺点深度剖析3.1 核心优点3.2 核心缺点生产踩坑重点四、CMS常见生产问题及解决方案实战重点4.1 问题1CMS频繁触发Full GC老年代内存不足现象原因解决方案4.2 问题2CMS导致CPU使用率过高现象原因解决方案4.3 问题3内存碎片导致大对象分配失败现象原因解决方案4.4 问题4重新标记阶段停顿时间过长现象原因解决方案五、面试官追问环节实战高频追问1CMS的并发标记和重新标记有什么区别为什么重新标记需要STW追问2CMS产生的浮动垃圾是什么如何减少浮动垃圾追问3CMS和G1收集器的核心区别是什么各自的适用场景是什么六、总结一、CMS垃圾收集器核心概述CMS 全称 Concurrent Mark Sweep并发标记-清除是一种基于“标记-清除”算法实现的垃圾收集器核心目标是缩短垃圾收集时的停顿时间适用于对响应时间要求高的场景如电商、接口服务也是JDK1.8及之前高并发系统的主流选择。它的核心特点是垃圾收集与用户线程并发执行大部分阶段仅在初始标记和重新标记两个阶段会产生短暂停顿这也是它“低停顿”优势的核心来源。提示CMS仅作用于老年代通常与新生代的ParNew收集器配合使用ParNew负责新生代回收CMS负责老年代回收二者协同完成整个JVM的垃圾回收。二、CMS核心工作流程5个阶段CMS的垃圾收集过程分为5个阶段其中3个阶段与用户线程并发执行2个阶段会产生停顿STWStop The World具体流程如下初始标记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 GCSerial Old收集器执行停顿时间极长。存在浮动垃圾并发标记阶段用户线程可能创建新对象、断开对象引用这些未被标记的垃圾浮动垃圾无法在本次GC中清除只能等到下一次GC可能导致老年代内存提前占满触发频繁GC。无法处理大对象由于内存碎片问题当需要分配大对象如超过100MB的数组时容易出现“内存不足”异常即使老年代总内存充足。GC停顿不可控虽然整体停顿短但重新标记阶段的停顿时间可能因对象数量过多而变长且并发阶段占用CPU可能间接导致应用响应变慢。提示点赞收藏本文后续遇到CMS相关的生产问题直接对照缺点排查效率翻倍四、CMS常见生产问题及解决方案实战重点在实际生产环境中CMS的缺点很容易引发各类问题以下是4个最常见的问题结合实战场景给出解决方案同时补充面试官高频追问。4.1 问题1CMS频繁触发Full GC老年代内存不足现象应用日志中频繁出现“CMS GC”记录且伴随“Full GC”接口响应时间突然变长甚至出现超时。原因老年代内存分配过小对象晋升速度过快新生代对象频繁晋升到老年代存在内存泄漏如静态集合持有大量对象引用未及时释放浮动垃圾过多导致老年代内存提前占满。解决方案调整JVM参数增大老年代内存-Xms、-Xmx设置更大值同时调整-XX:CMSInitiatingOccupancyFraction默认68%可调整为75%-80%推迟CMS触发时机排查内存泄漏使用JProfiler、MAT等工具分析堆内存快照找到未释放的大对象和内存泄漏点如静态Map未清理优化对象创建减少大对象创建避免短生命周期对象被长期引用如避免将局部对象存入静态集合。4.2 问题2CMS导致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个面试官最常追问的问题帮你提前准备从容应对面试。追问1CMS的并发标记和重新标记有什么区别为什么重新标记需要STW答并发标记与用户线程并发执行遍历GC Roots关联的对象标记存活对象但可能因用户线程修改引用导致标记偏差重新标记需要STW因为要修正并发标记阶段的标记偏差重新扫描所有GC Roots和被修改的对象确保标记结果准确——如果不STW用户线程继续修改引用标记结果会一直不准确导致垃圾回收遗漏或误删存活对象。追问2CMS产生的浮动垃圾是什么如何减少浮动垃圾答浮动垃圾并发标记阶段用户线程创建的新对象、断开引用的对象这些对象未被本次GC标记无法在本次GC中清除只能等到下一次GC称为浮动垃圾减少方法① 推迟CMS触发时机调整-XX:CMSInitiatingOccupancyFraction给浮动垃圾更多时间被回收② 减少短生命周期对象的创建降低并发阶段的对象修改频率③ 增大老年代内存避免浮动垃圾快速占满老年代。追问3CMS和G1收集器的核心区别是什么各自的适用场景是什么答核心区别① 算法不同CMS基于标记-清除有内存碎片G1基于标记-整理无内存碎片② 回收方式不同CMS仅回收老年代需配合ParNewG1可同时回收新生代和老年代③ 停顿控制不同CMS无法精确控制停顿时间G1可通过-XX:MaxGCPauseMillis设置最大停顿时间适用场景① CMS适用于JDK1.8及之前对响应时间敏感、CPU资源充足、不介意内存碎片的高并发场景② G1适用于JDK1.7及之后内存较大如8G以上、需要精确控制停顿时间、避免内存碎片的场景。六、总结CMS垃圾收集器是JVM中经典的并发收集器核心优势是低停顿适合高并发、响应时间敏感的场景但同时存在CPU消耗高、内存碎片、浮动垃圾等问题在生产环境中需重点关注参数调优和问题排查。本文从核心流程、优缺点、生产问题、面试追问四个维度全面解析CMS掌握这些知识点不仅能解决生产中的实际问题还能从容应对面试官的相关提问。