尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CMS收集器详解

CMS收集器详解 一、CMS 核心定位什么是 CMS 收集器1. 核心定义CMS 是 HotSpot 虚拟机中一款老年代垃圾收集器基于 “标记 - 清除” 算法实现核心特点是并发收集、低停顿—— 垃圾收集线程与用户线程大部分时间可并行执行大幅减少 STWStop The World时间提升应用响应速度。2. 通俗比喻把 JVM 老年代比作大型商场用户线程是购物的顾客GC 线程是清洁工人其他收集器如 Serial Old清洁时清场顾客全部暂停STW清洁完再让顾客进入停顿时间长CMS 收集器清洁工人与顾客并行工作 —— 先快速标记需要清洁的区域短暂清场然后一边让顾客购物一边清理垃圾仅在关键步骤短暂暂停顾客停顿时间极短。3. 适用场景与搭配适用场景响应时间优先的服务如 Web 网站、API 接口、微服务要求 GC 停顿时间控制在百毫秒内搭配收集器CMS 仅负责老年代回收新生代需搭配 ParNew 收集器Serial 收集器的多线程版本形成 “ParNewCMS” 组合JDK8 默认支持不适用场景吞吐量优先的场景如计算密集型任务、大内存场景如堆内存≥16GCMS 性能会下降。二、CMS 核心原理“标记 - 清除”“并发执行”1. 核心算法标记 - 清除CMS 基于 “标记 - 清除” 算法实现分为 “标记” 和 “清除” 两个核心阶段标记阶段识别老年代中存活的对象通过可达性分析算法以 GC Roots 为起点遍历清除阶段回收未被标记的垃圾对象释放内存空间。但传统 “标记 - 清除” 算法存在效率低、产生内存碎片的问题CMS 通过 “并发执行” 和 “分阶段优化” 解决了效率问题却保留了内存碎片的缺陷后续会详细说明。2. 核心设计并发执行CMS 的 “低停顿” 核心源于 “并发”—— 标记和清除阶段的大部分工作与用户线程并行执行仅在初始标记和重新标记两个关键步骤触发短暂 STW。这里要明确 “并发” 与 “并行” 的区别并发ConcurrentGC 线程与用户线程同时运行同一 CPU 核心交替执行并行Parallel多个 GC 线程同时运行利用多核 CPU 并行处理如 ParNew 收集器。CMS 是 “并发收集器”与用户线程并发同时也是 “并行收集器”多个 GC 线程并行执行标记 / 清除。三、CMS 完整工作流程4 个阶段 STW 关键点CMS 的垃圾回收过程分为 4 个阶段其中仅 2 个阶段需要 STW且停顿时间极短通常在几十毫秒内另外 2 个阶段与用户线程并发执行几乎不影响应用运行。1. 阶段 1初始标记Initial Mark—— STW短暂核心工作仅标记GC Roots 直接关联的对象如虚拟机栈中引用的对象、静态变量引用的对象不遍历整个引用链。通俗理解清洁工人先快速标记商场中 “入口处直接可见的垃圾”如门口的塑料袋不深入商场内部检查因此速度极快。特点STW 时间极短通常 10ms 内几乎无感知单线程执行早期版本后续版本支持多线程并行进一步缩短停顿。2. 阶段 2并发标记Concurrent Mark—— 并发无 STW核心工作从初始标记的对象出发遍历整个老年代的引用链标记所有存活的对象。通俗理解清洁工人在顾客购物的同时从门口的垃圾出发逐一检查商场内部的每个角落标记所有需要清理的垃圾顾客正常购物不受影响。特点与用户线程并发执行无 STW耗时最长占整个 GC 周期的 80% 以上但不影响应用响应可能产生 “浮动垃圾”并发标记期间用户线程可能修改对象引用如创建新对象、断开引用导致部分垃圾未被标记后续 GC 再清理。3. 阶段 3重新标记Remark—— STW短暂核心工作修正并发标记期间因用户线程操作导致的标记偏差重新标记被遗漏的存活对象。通俗理解清洁工人在顾客短暂暂停购物时快速核对之前的标记结果补充标记并发期间新增的存活对象如顾客临时放下的物品修正标记错误。特点STW 时间比初始标记稍长通常几十毫秒但仍远短于 Full GC采用多线程并行执行提升标记效率核心优化使用 “增量更新”Incremental Update机制处理并发标记期间的引用变化具体原理见下文 “关键优化”。4. 阶段 4并发清除Concurrent Sweep—— 并发无 STW核心工作遍历老年代内存回收所有未被标记的垃圾对象释放内存空间。通俗理解清洁工人在顾客恢复购物后清理所有之前标记的垃圾顾客正常购物不受影响。特点与用户线程并发执行无 STW不移动存活对象因此会产生内存碎片后续会讲解决方案回收过程中用户线程可继续创建新对象若老年代内存不足会触发 “Concurrent Mode Failure”并发模式失败转而执行 Serial Old 收集器的 Full GC停顿时间极长。总结CMS 工作流程时序图[初始标记STW10ms] → [并发标记无STW数百ms] → [重新标记STW50ms] → [并发清除无STW数百ms]整个 GC 周期中STW 总时间通常控制在 100ms 内远优于 Serial Old秒级停顿和 Parallel Old百毫秒秒级停顿。四、CMS 的关键优化增量更新Incremental Update在并发标记阶段用户线程可能修改对象引用如黑色对象新增对白色对象的引用、灰色对象断开对白色对象的引用导致 “漏标”白色对象本是存活的却被标记为垃圾。CMS 通过 “增量更新” 机制解决漏标问题核心逻辑当黑色对象已标记且引用链遍历完成新增对白色对象未标记的引用时通过 “写屏障” 记录该引用关系重新标记阶段以这些黑色对象为根再次遍历引用链补充标记遗漏的白色对象确保所有存活对象都被正确标记避免 “错杀” 对象。五、CMS 的优缺点低停顿的代价1. 优点核心竞争力1低停顿核心STW 时间极短适合响应时间优先的场景能显著提升用户体验如 Web 应用接口响应更快、无明显卡顿。2并发执行标记和清除阶段与用户线程并发不占用过多 CPU 资源对吞吐量影响较小。3多线程并行初始标记、重新标记阶段支持多线程并行充分利用多核 CPU 优势缩短 STW 时间。2. 缺点无法回避的问题1产生内存碎片基于 “标记 - 清除” 算法清除垃圾后会产生大量不连续的内存碎片。后果大对象无法分配连续内存即使老年代总内存充足也会触发 Full GC频繁的 Full GC 会导致应用卡顿抵消低停顿的优势。2并发模式失败Concurrent Mode Failure并发清除阶段用户线程持续创建新对象若老年代内存快速被占满CMS 无法及时回收会触发 “并发模式失败”JVM 会自动切换到 Serial Old 收集器执行 Full GC单线程、标记 - 整理算法STW 时间极长秒级。3占用额外 CPU 资源并发标记和清除阶段GC 线程与用户线程竞争 CPU 资源会导致应用吞吐量下降通常下降 10%~20%。在单核 CPU 环境下CMS 性能会严重退化。4产生浮动垃圾并发标记阶段用户线程创建的新对象或修改引用产生的垃圾无法在本次 GC 中回收需等到下次 GC 清理称为 “浮动垃圾”。浮动垃圾会占用老年代内存增加并发模式失败的风险。5不支持压缩整理CMS 仅做 “标记 - 清除”不移动存活对象无法解决内存碎片问题后续会讲优化方案。六、CMS 实战调优参数配置与问题解决1. 核心 JVM 参数启用与基础配置1启用 CMS 收集器-XX:UseConcMarkSweepGC # 启用CMS收集器老年代 -XX:UseParNewGC # 新生代使用ParNew收集器与CMS搭配启用后JVM 默认新生代用 ParNew、老年代用 CMS形成 “ParNewCMS” 组合。2内存相关参数-Xms4g -Xmx4g # 堆内存初始/最大4G -Xmn2g # 新生代2G堆的50%减少老年代压力 -XX:SurvivorRatio8 # 新生代Eden:S0:S18:1:1默认 -XX:MaxTenuringThreshold15 # 对象晋升老年代年龄阈值默认153CMS 核心调优参数参数作用推荐配置-XX:CMSInitiatingOccupancyFraction老年代使用率阈值达到该值触发 CMS GC默认 92%80~85提前触发避免并发模式失败-XX:UseCMSInitiatingOccupancyOnly仅按上述阈值触发 CMS不动态调整启用-XX:UseCMSInitiatingOccupancyOnly-XX:ParallelCMSThreadsCMS 并发线程数默认 CPU 核心数 3/4按 CPU 核心数调整如 8 核设为 2~4-XX:CMSFullGCsBeforeCompaction执行 N 次 Full GC 后进行一次内存整理解决碎片0~3默认 0即每次 Full GC 后整理-XX:CMSCompactAtFullCollectionFull GC 时进行内存整理压缩启用默认启用-XX:CMSMaxAbortablePrecleanTime预清理阶段最大时间默认 5 秒5000ms根据业务调整2. 常见问题与解决方案1并发模式失败Concurrent Mode Failure现象日志中出现Concurrent Mode Failure随后触发 Serial Old Full GC停顿时间极长。原因老年代使用率阈值设置过高默认 92%CMS 启动过晚并发清除阶段内存不足新生代对象晋升过快老年代被快速占满浮动垃圾过多占用老年代内存。解决方案降低-XX:CMSInitiatingOccupancyFraction至 80~85让 CMS 提前触发增大老年代内存如-Xmx从 4G 调至 6G或调整新生代比例-Xmn减少对象晋升优化应用代码减少大对象创建和长期存活对象。2内存碎片过多现象老年代总内存充足但频繁触发 Full GC日志中显示 “老年代碎片率高”。原因CMS “标记 - 清除” 算法产生的内存碎片导致大对象无法分配连续内存。解决方案启用-XX:CMSCompactAtFullCollection默认启用Full GC 时自动整理内存通过-XX:CMSFullGCsBeforeCompaction3每执行 3 次 Full GC 后强制整理一次平衡停顿时间和碎片率避免创建过大的对象如超过老年代内存的 10%或通过-XX:PretenureSizeThreshold设置大对象阈值让大对象直接进入老年代减少晋升压力。3重新标记阶段 STW 时间过长现象重新标记阶段 STW 时间超过 100ms影响应用响应。原因并发标记期间用户线程修改的引用过多重新标记需要遍历大量对象CMS 并发线程数不足标记效率低。解决方案增加-XX:ParallelCMSThreads线程数如 8 核 CPU 设为 4提升并行标记效率启用-XX:CMSScavengeBeforeRemark重新标记前先执行一次 Minor GC减少老年代引用的新生代对象缩短标记时间优化应用代码减少并发标记期间的对象引用修改如避免在高并发场景下频繁修改静态变量引用。3. CMS 日志分析示例启用 CMS 后GC 日志会包含如下关键信息简化版# 初始标记STW [GC (CMS Initial Mark) [1 CMS-initial-mark: 15360K(30720K)] 17920K(40960K), 0.0050000 secs] [Times: user0.02 sys0.00, real0.01 secs] # 并发标记 [CMS-concurrent-mark-start] [CMS-concurrent-mark: 0.2000000 secs] [Times: user0.50 sys0.10, real0.20 secs] # 重新标记STW [GC (CMS Final Remark) [YG occupancy: 2560K (10240K)] [Rescan (parallel) , 0.0300000 secs] [Weak Ref Processing, 0.0010000 secs] [Class Unloading, 0.0020000 secs] [Scrub Symbol Table, 0.0030000 secs] [Scrub String Table, 0.0010000 secs] [1 CMS-remark: 15360K(30720K)] 17920K(40960K), 0.0370000 secs] [Times: user0.10 sys0.01, real0.04 secs] # 并发清除 [CMS-concurrent-sweep-start] [CMS-concurrent-sweep: 0.1500000 secs] [Times: user0.40 sys0.08, real0.15 secs]CMS-initial-mark初始标记STW 时间 0.01 秒CMS-concurrent-mark并发标记耗时 0.2 秒无 STWCMS Final Remark重新标记STW 时间 0.04 秒CMS-concurrent-sweep并发清除耗时 0.15 秒无 STW总 STW 时间仅 0.05 秒符合低停顿目标。七、CMS 与 G1 收集器的对比为什么 CMS 会被淘汰JDK9 后 G1 取代 CMS 成为默认收集器核心原因是 G1 解决了 CMS 的诸多缺陷同时保留了低停顿优势。两者关键对比对比项CMS 收集器G1 收集器核心算法标记 - 清除老年代标记 - 整理局部复制全局整理内存碎片有严重无内存连续停顿可预测性不可预测可预测通过-XX:MaxGCPauseMillis设置内存布局新生代 老年代物理隔离Region 分区逻辑分代物理不隔离并发模式失败易触发切换 Serial Old不易触发动态调整 Region大内存支持差堆≥16G 性能下降好堆≥32G 仍高效吞吐量中等并发占用 CPU较高优化的并行执行结论若使用 JDK9优先选择 G1 收集器无需纠结 CMS若维护 JDK8 及以前的老系统且对响应时间要求高可使用 CMS但需做好调优避免并发模式失败和内存碎片计算密集型、吞吐量优先的场景推荐使用 Parallel ScavengeParallel Old 组合而非 CMS。
返回列表