CMS与G1垃圾回收器深度剖析

发布时间:2026/7/30 4:49:17

CMS与G1垃圾回收器深度剖析 CMS与G1垃圾回收器深度剖析前言在前面的系列文章中我们深入探讨了MinorGC、FullGC的触发机制和执行过程。但GC的世界里真正的主角是垃圾回收器本身。CMS和G1是Java生态中最经典、最常用的两款垃圾回收器CMS低停顿的开拓者重视响应速度G1可预测停顿的集大成者兼顾吞吐量和低延迟今天我们就来深度剖析这两款回收器的设计思想、核心流程、以及各自的优缺点。一、垃圾回收器概述1.1 垃圾回收器的演进JDK版本 回收器演进 ───────────────────────────────────────────────── JDK 1.3 Serial串行→ 单线程适合客户端 JDK 1.4 Parallel并行→ 多线程重视吞吐量 JDK 1.5 CMS并发→ 低停顿重视响应速度 JDK 7 G1Garbage First→ 可预测停顿 JDK 9 G1成为默认回收器 JDK 11 ZGC、Shenandoah → 亚毫秒级停顿1.2 回收器分类分类回收器特点适用场景新生代Serial、ParNew、Parallel Scavenge复制算法根据不同需求选择老年代CMS、Serial Old、Parallel Old标记-清除/标记-整理配合新生代使用全堆G1、ZGC、ShenandoahRegion化大内存、低延迟二、CMSConcurrent Mark Sweep回收器2.1 设计目标以获取最短回收停顿时间为目标重视响应速度适合互联网站、B/S系统等对延迟敏感的应用。2.2 CMS的核心流程CMS的GC过程分为4个阶段其中两个阶段会STWStop The World┌─────────────────────────────────────────────────────────────┐ │ CMS回收流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. 初始标记 (Initial Mark) - STW │ │ ├─ 标记GC Roots能直接关联到的对象 │ │ └─ 速度很快停顿时间短 │ │ ↓ │ │ 2. 并发标记 (Concurrent Mark) - 并发 │ │ ├─ 从GC Roots开始遍历整个对象图 │ │ ├─ 耗时最长但和用户线程一起执行 │ │ └─ 用户线程继续运行不暂停 │ │ ↓ │ │ 3. 重新标记 (Remark) - STW │ │ ├─ 修正并发标记期间因用户程序继续运行而变动的对象 │ │ ├─ 停顿时间比初始标记稍长但远小于并发标记 │ │ └─ 使用增量更新算法解决漏标问题 │ │ ↓ │ │ 4. 并发清除 (Concurrent Sweep) - 并发 │ │ ├─ 清除标记为死亡的对象 │ │ └─ 和用户线程一起执行不暂停 │ │ │ └─────────────────────────────────────────────────────────────┘2.3 CMS的优缺点✅ 优点低停顿只有初始标记和重新标记需要STW且停顿时间短并发执行最耗时的标记和清除阶段都与用户线程并发响应快适合对延迟敏感的应用❌ 缺点CPU敏感并发阶段会占用CPU资源可能导致应用变慢无法处理浮动垃圾并发标记期间新产生的垃圾只能留到下次GC内存碎片使用标记-清除算法会产生内存碎片并发模式失败老年代在并发标记期间被填满会退化为Serial Old FullGC2.4 CMS的关键问题问题1浮动垃圾Floating Garbage时间线 ─────────────────────────────────────────────────→ 并发标记开始 并发标记结束 ↓ ↓ ├────────────────────────┤ 用户线程持续运行不断产生新对象 这些新对象在本次GC中无法被清除 ↓ 这些对象成为浮动垃圾等待下次GC问题2并发模式失败Concurrent Mode Failure// 场景老年代在CMS完成前被填满触发条件老年代使用率-XX:CMSInitiatingOccupancyFraction默认92% 后果1.CMS并发回收来不及完成2.暂停所有用户线程3.退化为SerialOld回收器单线程、标记-整理4.触发FullGC耗时极长问题3内存碎片标记-清除算法导致 ┌─────────────────────────────────────────┐ │ [存活] [空闲] [存活] [空闲] [存活] [空闲] │ ← 碎片化严重 └─────────────────────────────────────────┘ 后果 - 分配大对象时找不到连续空间 - 提前触发FullGC解决方案开启内存碎片整理-XX:UseCMSCompactAtFullCollection# FullGC后进行碎片整理-XX:CMSFullGCsBeforeCompaction0# 每次FullGC后都整理2.5 CMS参数调优# 启用CMS-XX:UseConcMarkSweepGC# 设置触发CMS的阈值默认92%-XX:CMSInitiatingOccupancyFraction75# 开启碎片整理-XX:UseCMSCompactAtFullCollection# 设置多少次FullGC后整理碎片-XX:CMSFullGCsBeforeCompaction0# 设置CMS并发线程数-XX:ConcGCThreads4# 禁用显式GC避免System.gc()触发CMS-XX:DisableExplicitGC三、G1Garbage First回收器3.1 设计目标建立可预测的停顿时间模型。你可以指定一个期望的停顿时间如200msG1会尽量保证每次GC停顿不超过这个时间。3.2 G1的核心设计RegionG1最大的创新是不再坚持物理分代而是将堆划分为多个大小相等的独立Region堆内存布局G1 ┌─────────────────────────────────────────────────────────────┐ │ Region 0 │ Region 1 │ Region 2 │ Region 3 │ ... │ ├────────────┼────────────┼────────────┼────────────┼────────┤ │ Eden │ Survivor │ Eden │ Humongous │ Old │ │ Region │ Region │ Region │ Region │ Region │ └────────────┴────────────┴────────────┴────────────┴────────┘ 每个Region的大小1MB ~ 32MB由JVM自动决定或通过-XX:G1HeapRegionSize设置Region的类型Eden Region存放新创建的对象Survivor Region存放经历GC后存活的对象Old Region存放晋升到老年代的对象Humongous Region存放超过Region大小50%的大对象3.3 G1的核心流程G1的GC分为Young GC和Mixed GC混合回收┌─────────────────────────────────────────────────────────────┐ │ G1回收流程 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1. Young GC新生代回收 │ │ ├─ 触发条件Eden Region用满 │ │ ├─ 回收所有Eden Region和Survivor Region │ │ ├─ 存活对象复制到新的Survivor Region或晋升到Old Region │ │ └─ STW但停顿时间可控 │ │ ↓ │ │ 2. 并发标记Concurrent Marking │ │ ├─ 初始标记STW短标记GC Roots │ │ ├─ 并发标记并发遍历对象图 │ │ ├─ 最终标记STW短处理SATB缓冲区 │ │ └─ 筛选回收STW评估各Region的回收价值 │ │ ↓ │ │ 3. Mixed GC混合回收 │ │ ├─ 触发条件老年代占用率达到阈值 │ │ ├─ 回收所有Young Region 部分Old Region │ │ ├─ 选择回收价值最高的RegionGarbage First名字来源 │ │ └─ 停顿时间控制在用户设定的范围内 │ │ │ └─────────────────────────────────────────────────────────────┘3.4 G1的核心技术技术1SATBSnapshot-At-The-BeginningG1在并发标记开始时会记录一个快照标记所有存活对象。并发标记期间用户线程对引用的修改会被记录到SATB缓冲区最终标记时再处理。并发标记开始时 ┌─────────────────────────────────────────┐ │ 对象A → 对象B (存活快照) │ └─────────────────────────────────────────┘ 并发标记期间 用户线程修改对象A → 对象C新对象 ↓ G1记录这个变化到SATB缓冲区 ↓ 最终标记阶段处理缓冲区技术2停顿预测模型G1会记录每次GC的耗时建立统计模型预测每个Region的回收时间从而选择回收价值最高的Region集合。停顿时间目标200ms ↓ G1计算回收Region A需要50msRegion B需要80msRegion C需要100ms ↓ 选择Region A Region B 130ms在200ms内 放弃Region C时间不够技术3RSetRemembered Set每个Region都有一个RSet记录哪些Region中的对象引用了本Region的对象。这样在进行垃圾回收时不需要扫描整个堆就能找到跨Region引用。Region A Region B ┌─────────────┐ ┌─────────────┐ │ 对象X │ │ 对象Y │ │ 引用 ───────────────────→│ │ └─────────────┘ └─────────────┘ ↑ Region B的RSet记录被Region A的对象X引用3.5 G1的优缺点✅ 优点可预测的停顿用户可以指定期望的停顿时间无内存碎片使用复制算法Region间复制并发收集大部分阶段与用户线程并发大内存友好适合4GB以上的堆内存❌ 缺点内存开销大每个Region的RSet占用额外内存约5%-10%CPU开销维护RSet需要额外计算FullGC风险如果Mixed GC跟不上对象分配速度仍会退化为FullGC3.6 G1参数调优# 启用G1-XX:UseG1GC# 设置期望的停顿时间默认200ms-XX:MaxGCPauseMillis200# 设置Region大小1MB-32MB-XX:G1HeapRegionSize16m# 设置并发线程数-XX:ConcGCThreads4# 设置触发Mixed GC的阈值默认45%-XX:InitiatingHeapOccupancyPercent45# 设置Mixed GC中Old Region的比例-XX:G1MixedGCLiveThresholdPercent85# 禁用显式GC-XX:DisableExplicitGC四、CMS vs G1 对比4.1 核心差异对比维度CMSG1设计目标最短停顿时间可预测的停顿时间堆内存布局物理分代Eden、Survivor、Old逻辑分代Region回收算法标记-清除老年代复制算法Region间内存碎片有无停顿时间低但不可预测可控可预测CPU开销较高较高适用堆大小2GB-4GB4GBFullGC风险并发模式失败分配速度跟不上时4.2 停顿时间对比停顿时间对比示意 ─────────────────────────────────────────────────→ CMS: [STW] [并发] [STW] [并发] ↑短 ↑长 ↑稍长 ↑并发清除 G1: [STW] [并发] [STW] [STW] ↑短 ↑长 ↑短 ↑筛选回收部分Region 停顿时间总和可控4.3 适用场景建议场景推荐回收器理由堆内存 4GBCMSG1的RSet开销相对较大小堆不划算堆内存 4GBG1G1的Region化设计更适合大堆对停顿时间敏感G1可预测的停顿时间对CPU敏感CMSG1维护RSet消耗更多CPU需要避免FullGCG1通过Mixed GC提前回收减少FullGCJDK 9G1默认回收器持续优化五、实战如何选择合适的GC5.1 选择决策树开始 ↓ 堆内存 4GB ├─ 是 → 选择CMS └─ 否 → 继续 ↓ 对停顿时间有明确要求如200ms ├─ 是 → 选择G1设置MaxGCPauseMillis └─ 否 → 继续 ↓ 追求最大吞吐量 ├─ 是 → 选择Parallel Scavenge Parallel Old └─ 否 → 选择G1默认5.2 GC日志分析示例CMS正常日志[GC (Allocation Failure) [ParNew: 51200K-5120K(58880K), 0.010s] [CMS: 102400K-51200K(204800K), 0.050s] 153600K-56320K(263680K), 0.060s]CMS并发模式失败[Full GC (Concurrent Mode Failure) [ParNew: 51200K-5120K(58880K)] [CMS: 204800K-204800K(204800K)] 256000K-209920K(263680K), 0.823s]注意CMS回收后老年代仍然是满的204800K-204800K触发FullGC。G1正常日志[GC pause (G1 Evacuation Pause) (young), 0.025s] [GC pause (G1 Evacuation Pause) (mixed), 0.035s] [GC concurrent-root-region-scan-start] [GC concurrent-root-region-scan-end, 0.005s]G1 FullGC[Full GC (Allocation Failure) [PSYoungGen: 51200K-0K(58880K)] [ParOldGen: 204800K-204800K(204800K)] 256000K-204800K(263680K), 0.523s]六、总结6.1 核心要点速记回收器一句话总结CMS低停顿的开拓者重视响应速度但有内存碎片和并发模式失败风险G1可预测停顿的集大成者Region化设计适合大内存应用6.2 CMS vs G1 选择建议选择CMS的场景 ├─ 堆内存 4GB ├─ 对CPU敏感不希望额外开销 └─ JDK 8及以下 选择G1的场景 ├─ 堆内存 4GB ├─ 需要可预测的停顿时间 ├─ 希望避免FullGC └─ JDK 9默认6.3 调优参数速查# CMS参数-XX:UseConcMarkSweepGC# 启用CMS-XX:CMSInitiatingOccupancyFraction75# 触发CMS的阈值-XX:UseCMSCompactAtFullCollection# 开启碎片整理# G1参数-XX:UseG1GC# 启用G1-XX:MaxGCPauseMillis200# 期望停顿时间-XX:G1HeapRegionSize16m# Region大小-XX:InitiatingHeapOccupancyPercent45# 触发Mixed GC的阈值七、面试金句如果面试官问你CMS和G1的区别你可以这样回答“CMS和G1都是低停顿垃圾回收器但设计理念不同。CMS以最短停顿为目标采用标记-清除算法只有初始标记和重新标记需要STW但会产生内存碎片且并发模式失败时会退化为Serial Old FullGC。G1以可预测停顿为目标将堆划分为多个Region通过筛选回收价值最高的Region来控制停顿时间使用复制算法避免碎片但需要额外维护RSet内存开销更大。选择上堆内存小于4GB时CMS更合适大于4GB时G1更有优势。在JDK 9中G1已成为默认回收器。”如果你觉得本文有帮助欢迎点赞、评论、转发你的支持是我持续输出的动力。

相关新闻