CMS圣经:CMS垃圾回收器的原理、调优,多标+漏标+浮动垃圾 分析与 研究

CMS垃圾回收器原理、调优及问题分析

本文的 原始地址 ,传送门

本文的 原始地址 ,传送门

尼恩说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:

听说你是高手,说说,你的CMS怎么调优?

说说,CMS 垃圾回收器的底层原理?

说说,CMS 的浮动垃圾,是怎么处理的?

说说,CMS 垃圾回收器的调优过程?

最近有小伙伴在面试 美团,又遇到了相关的面试题。小伙伴懵了,因为没有遇到过,所以支支吾吾的说了几句,面试官不满意,面试挂了。

所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。

当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V171版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,回复:领电子书

另外,此文的内容,作为0第10章,收入尼恩的《JVM 调优圣经》PDF。

第10章:cms 底层原理和调优实战

《cms 底层原理和调优实战》内容正在写作中,本月底发布。

尼恩希望通过 JVM调优圣经一个PDF,帮助大家一举成为 JVM调优 小王子。

实现通过JVM调优 的超级技能,去毒打面试官。

完整的PDF还在写作中,晚些时候进行发布。

说在最后:有问题找老架构取经

《JVM 调优圣经》PDF 第11章:

美团面试:G1 垃圾回收 底层原理是什么?说说你的调优过程?

接下来,咱们言归正传,开始讲 cms

面试背景

CMS(Concurrent Mark Sweep)作为老年代垃圾回收器,以‌低延迟‌为核心目标,适用于对响应时间敏感的系统。尽管G1、ZGC等新回收器逐渐普及,但CMS仍广泛存在于传统企业级应用中。面试官提问CMS底层原理及调优,主要考察以下能力:

(1) 对并发垃圾回收机制的理解(如如何减少STW时间)‌;
(2) 实际调优经验(参数调整、问题定位能力)‌;
(3) 对JVM内存模型与GC算法的综合掌握程度(如卡表机制、碎片处理)‌。

回答核心要点

CMS底层原理

四阶段工作流程

  • ‌**初始标记(Initial Mark)**‌:STW阶段,标记GC Roots直接关联的对象‌;
  • ‌**并发标记(Concurrent Mark)**‌:与用户线程并发执行,遍历老年代对象引用链‌;
  • ‌**重新标记(Remark)**‌:STW阶段,修正并发标记期间变动的引用关系(通过增量更新或原始快照算法)‌;
  • ‌**并发清除(Concurrent Sweep)**‌:删除无引用对象,回收内存空间‌。

关键技术机制

  • ‌**卡表(Card Table)**‌:将老年代划分为512字节的卡片,记录跨代引用,避免YGC时扫描整个老年代‌;
  • 增量并发‌:通过交替执行GC线程与用户线程减少STW时间,但可能引发并发失败(Concurrent Mode Failure)‌。

CMS调优策略

核心参数调整

  • -XX:CMSInitiatingOccupancyFraction=70:老年代内存占用达70%时触发CMS回收,避免Full GC‌;
  • -XX:+UseCMSCompactAtFullCollection:Full GC时压缩内存碎片(默认关闭,需权衡性能)‌;
  • -XX:+CMSParallelRemarkEnabled:启用并行重新标记,减少Remark阶段耗时‌。

典型问题场景调优

  • 频繁Concurrent Mode Failure
    表现:触发Serial Old回收器导致长STW。
    解决:增大老年代空间或降低CMSInitiatingOccupancyFraction阈值‌。
  • 内存碎片严重
    表现:老年代剩余空间足够但无法分配大对象(晋升失败)。
    解决:启用UseCMSCompactAtFullCollection或周期性Full GC‌。
  • Remark阶段耗时高
    解决:通过CMSScavengeBeforeRemark在Remark前触发YGC,减少跨代引用跟踪负担‌。

调优优先级策略

  • 先监控后调优‌:使用jstat -gcutil观察GC频率/耗时,jmap分析内存分布‌;
  • 扩容优先‌:若硬件成本允许,优先增加内存而非复杂调优(符合生产环境常见做法)‌。

回答策略

结构化表达‌:按“原理→问题→解决方案”逻辑展开,如先说明CMS阶段,再针对各阶段常见问题给出调优方法‌;

结合场景举例‌:如描述某次线上服务因碎片问题导致Full GC耗时增加,通过调整CMSFullGCsBeforeCompaction解决‌;

强调权衡意识‌:如低延迟与吞吐量的取舍、碎片压缩与暂停时间的平衡‌;

关联新技术‌:对比CMS与G1/ZGC的优劣,体现技术视野(如CMS适用于中小堆,ZGC适合超大堆)‌。

避坑指南

避免过度调优:强调JVM默认参数在多数场景已足够可靠,调优需以监控数据为依据‌;

勿混淆概念:区分卡表(Card Table)与记忆集(Remembered Set),前者用于跨代引用,后者用于分代收集器的跨区域引用‌。

CMS解决了什么问题?

CMS 的核心价值在于 ‌以并发回收机制实现毫秒级停顿‌,尤其适合中小规模堆内存下对延迟敏感的场景(如实时服务)‌。但其内存碎片和 CPU 资源占用问题需通过参数调优规避,且在超大堆(≥8GB)或长期运行场景中,G1 或 ZGC 可能是更优选择‌。

CMS(Concurrent Mark Sweep)垃圾回收器主要解决以下问题:

  • 减少垃圾回收停顿时间

  • 高吞吐量与低延迟的平衡

  • 大堆内存管理

  • 解决内存碎片问题

  • 并发标记和清除

  • 处理对象引用变化

什么是CMS?

CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。

在启动 JVM 的参数加上-XX:+UseConcMarkSweepGC来指定使用 CMS 垃圾回收器。

CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。

CMS(Concurrent Mark Sweep)是基于‌标记-清除算法‌的老年代垃圾收集器,设计目标为‌最小化停顿时间‌‌。其核心思想是通过‌并发标记与清理‌减少STW(Stop The World)时长,适用于对延迟敏感的应用场景(如Web服务)‌。

9款 垃圾回收器 的JDK版本

了解下 HotSpot虚拟机中 9款 垃圾回收器 的发布时间及其对应的 JDK版本,如下图:

版本 发布时间 默认收集器 事件
jdk1.3 2000-05-08 serial
jdk1.4 2004-02-06 ParNew
jdk1.5/5.0 2004-09-30 Parallel Scavenge/serial CMS登场
jdk1.6/6.0 2006-12-11 Parallel Scavenge/Parallel Old
dk1.7/7.0 2011-07-28 Parallel Scavenge/Parallel Old G1登场
jdk1.8/8.0 2014-03-18 Parallel Scavenge/Parallel Old
jdk1.9/9.0 2014-09-8 G1 CMS废弃
jdk10 2018-03-21 G1
jdk11 2018-09-25 G1 ZGC登场
jdk12 2019-3 G1 Shenandoah
jdk13 2019-9 G1
jdk14 2020-3 G1 CMS移除
jdk15 2020-9-15 G1 ZGC、Shenandoah转正
jdk16 2021-3-16 G1
jdk17 2021-09-14 G1
jdk21 2022-3-22 G1 ZGC分代
jdk23 2022-9-22 ZGC

JDK 1.4.1 时 ,CMS 垃圾收集器被引入,在2020年3月,JDK 14 版本,CMS从 JDK中移除。

G1垃圾收集器 在 JDK 7 时引入,在 JDK 9 时G1取代 CMS 成为了默认的垃圾收集器。

就目前来说,JVM 的垃圾收集器主要分为两大类:分代收集器和分区收集器,分代收集器的代表是 CMS,分区收集器的代表是 G1 和 ZGC,下面我们来看看这两大类的垃圾收集器。

如下图:

说明:分代垃圾收集器中,新生代有 Serial、ParNew、Parallel Scavenge,老年代包括 CMS、MSC、Parallel old,收集器之间的连线说明两者可以搭配使用。

CMS‌的核心机制‌是什么?

(1) ‌并发标记与清理‌:

通过多线程与用户线程并发执行标记和清理操作,仅初始标记和重新标记阶段需短暂STW‌

(2) ‌三色标记法‌:

基于黑(已标记且存活)、灰(标记中)、白(未标记或垃圾)的对象状态跟踪,结合‌写屏障(Write Barrier)‌记录并发阶段的对象引用变化,防止漏标‌

(3) ‌内存碎片处理‌:

标记- 清除算法不压缩内存 ,长期运行后可能产生碎片,依赖Full GC(Serial Old)或参数触发碎片整理‌**

CMS‌的特点:

  • 并发收集:GC 线程与用户线程并发执行
  • 低停顿:追求最短回收停顿时间
  • 标记-清除算法:会产生内存碎片
  • 分代收集:与 ParNew 配合使用(年轻代用 ParNew,老年代用 CMS)

CMS 几个基本数据结构

结构1:卡表

为什么需要卡表?

CMS垃圾回收器需要卡表的核心原因在于‌优化跨代引用扫描效率‌,解决跨代引用扫描问题的效率问题:

1 跨代引用带来的性能瓶颈

在YGC(年轻代垃圾回收)时,老年代对象可能引用年轻代对象。

若直接扫描整个老年代寻找 跨代引用,时间和资源消耗将无法接受‌。

2 并发标记阶段,记录引用变化

CMS 并发标记阶段中,‌老年代引用变化会通过写屏障标记到卡表‌,核心目的是在重新标记阶段高效修正并发期间遗漏的存活对象标记。

卡表在此场景下不仅用于跨代引用跟踪,还直接服务于老年代内部引用变更的记录与处理‌

3 卡表的核心作用

卡表通过‌**将老年代划分为固定大小的卡片(Card,通常512字节)**‌,并记录卡片是否被修改(即脏卡标记)。

YGC时仅需扫描标记为脏的卡片,而非全量扫描老年代,大幅降低扫描范围‌。

卡表 在 YGC 的时候, 贡献很大。

什么是卡表?

对于分代垃圾回收器,势必存在一个跨代引用的问题,

而卡表就是最常用的一种跨代引用 记录结构 ,它是一个字节数组,用于记录堆内存的映射关系,下面是 HotSpot虚拟机默认的卡表标记逻辑:

// >> 9 代表右移 9位,即 2^9 = 512 字节
CARD_TABLE[this address >> 9] = 0;

每个元素都对应着其标识的内存区域中一块特定大小的内存块,这个内存块叫做“卡页(Card Page)”。

因为卡页代表的是一个区域,所以可能存在很多对象,只要有一个对象存在跨代引用,就把数组的值设为1,称该元素“变脏(Dirty)”,该卡页叫“脏页(Dirty Page)”,

如下: 1 2 当垃圾回收时,只要筛选卡表中有变脏的元素,即数组值为 1,就能判断出其对应的内存区域存在对象跨代引用,卡表和卡页的关系如下图:

在CMS(Concurrent Mark-Sweep)垃圾收集器中,卡表(Card Table)的主要作用是记录老年代对象对年轻代对象的引用,以加速年轻代垃圾收集(YGC)的过程。在YGC时,卡表帮助快速定位哪些老年代对象可能引用了年轻代对象,从而避免扫描整个老年代,提升效率。

在Full GC(全局垃圾收集)的场景中,卡表的贡献相对有限,因为Full GC会扫描整个堆内存,包括年轻代和老年代,确保所有不可达对象都被回收。具体来说:

1 Full GC的全面扫描

Full GC会遍历整个堆内存,标记所有存活对象,并回收所有不可达对象。由于Full GC已经进行了全局扫描,卡表的作用被弱化,因为不再需要依赖卡表来定位跨代引用。

2 卡表的维护

尽管在Full GC中卡表的作用不明显,但卡表仍然会被维护,以确保在后续的YGC中能够正常工作。Full GC会清理卡表,确保其内容与堆内存的实际情况一致。

3 性能影响

在Full GC期间,卡表的维护可能会带来一定的开销,但这种开销通常较小,因为Full GC的主要性能瓶颈在于全局扫描和对象回收。

总结来说,在Full GC的场景中,卡表的贡献较小,因为Full GC已经通过全局扫描处理了所有对象引用。卡表的主要作用仍然体现在YGC中,帮助加速跨代引用的处理。

结构2:写屏障

在 HotSpot虚拟机中,写屏障本质上是引用字段被赋值这个事件的一个环绕切面(Around AOP),即一个引用字段被赋值的前后可以为程序提供额外的动作(比如更新卡表)

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值