🔴 CMS收集器概述 #JVM/垃圾回收
🔴 CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,采用标记清除算法,实现了垃圾回收与用户线程的并发执行。
🟠 CMS核心特点
🟠 设计目标:最短回收停顿时间
🟠 算法基础:标记清除算法
🟠 并发特性:并发标记和并发清除阶段与用户线程并发执行
🟠 适用场景:对响应时间要求较高的应用
🔴 CMS回收流程详解
🔴 CMS垃圾回收过程分为4个阶段:
🟠 1. 初始标记 (CMS Initial Mark) - STW
- 🟠 目的:标记GC Roots直接关联的对象
- 🟠 特点:不需要Tracing,速度很快
- 🟠 停顿:需要STW(Stop The World)
- 🟡 线程数:JDK7及以前单线程,JDK8之后默认多线程
🟠 2. 并发标记 (CMS Concurrent Mark) - 并发
- 🟠 目的:进行GC Roots Tracing,标记所有可达对象
- 🟠 特点:与用户线程并发执行
- 🟠 时间:耗时最长,但不会造成应用停顿
🟠 3. 重新标记 (CMS Remark) - STW
- 🟠 目的:修正并发标记期间因用户程序变动而改变的对象
- 🟠 特点:需要STW,但时间相对较短
- 🟠 优化:通过预处理减少重新标记的时间
🟠 4. 并发清除 (CMS Concurrent Sweep) - 并发
- 🟠 目的:清除不可达对象,回收内存空间
- 🟠 特点:与用户线程并发执行
- 🟡 浮动垃圾:清除过程中产生的新垃圾,留待下次清理
🟡 CMS优缺点分析
🟢 优点
- 🟢 并发收集:大部分时间与用户线程并发执行
- 🟢 低停顿:显著减少垃圾回收造成的应用停顿时间
- 🟢 适合交互式应用:对响应时间敏感的应用场景
🔴 缺点
- 🔴 内存碎片:标记清除算法会产生大量空间碎片
- 🔴 吞吐量降低:并发阶段会降低系统整体吞吐量
- 🔴 CPU敏感:并发标记和清除需要额外的CPU资源
- 🔴 浮动垃圾:并发清除期间产生的垃圾无法及时回收
🔴 重要说明:由于整个过程中,并发标记和并发清除阶段收集器线程可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行的。
🟡 CMS常见问题解答
🟡 问题1:为什么CMS初始标记阶段是单线程的?
🟡 原因:初始化标记阶段是串行的,这是JDK7的行为。JDK8以后默认是并行的。
🟡 控制参数:
-XX:+CMSParallelInitialMarkEnabled
:启用并行初始标记
🔴 CMS的两种工作模式
🟠 Background CMS(后台CMS)
🟠 Background CMS是CMS的正常工作模式,标准流程包含4个主要阶段,预处理是优化措施:
🟡 1. 初始标记 (Initial Mark)
- 🟡 标记GC Roots直接关联的对象
- 🟡 需要STW,但时间很短
🟡 2. 并发标记 (Concurrent Mark)
- 🟡 进行GC Roots Tracing
- 🟡 与用户线程并发执行
🟡 3. 重新标记 (Remark)
- 🟡 修正并发标记期间的变化
- 🟡 需要STW,但时间相对较短
🟡 4. 并发清除 (Concurrent Sweep)
- 🟡 清除不可达对象
- 🟡 与用户线程并发执行
🟢 CMS预处理优化阶段
🟢 CMS还包含预处理阶段来优化重新标记的效率:
🟢 并发预处理 (Concurrent Preclean)
- 🟢 目的:减少重新标记阶段的工作量
- 🟢 触发条件:Eden空间使用超过阈值时启动
- 🟢 关键参数:
CMSScheduleRemarkEdenSizeThreshold
:默认2MCMSScheduleRemarkEdenPenetration
:默认50%
🟢 可中止的预处理 (Abortable Preclean)
- 🟢 目的:等待Minor GC发生,减少新生代对象数量
- 🟢 中止条件:
- 发生Minor GC时自动中止
- 达到最大时间限制时强制中止
- 🟢 关键参数:
CMSMaxAbortablePrecleanTime
:默认5秒CMSScavengeBeforeRemark
:强制在Remark前执行Minor GC
🔴 新生代扫描问题详解
🔴 核心问题:CMS是老年代垃圾回收器,为什么需要扫描新生代?
🔴 原因分析:
- 🔴 老年代对象可能被新生代对象引用
- 🔴 必须扫描新生代才能确定老年代对象的可达性
- 🔴 全量扫描新生代会很慢,与CMS低停顿目标冲突
🔴 解决方案:
- 🔴 先执行Minor GC,减少新生代对象数量
- 🔴 通过预处理阶段优化扫描效率
- 🔴 使用记忆集和卡表技术优化跨代引用
🔴 记忆集与卡表技术
🟠 记忆集 (Remembered Set)
🟠 问题背景:
- 🟠 Young GC时,除了常见的GC Roots(栈引用、静态变量、常量、锁对象、Class对象)
- 🟠 如果老年代对象引用了新生代对象,老年代对象也应加入GC Roots
- 🟠 但每次Young GC都扫描老年代代价太大
🟠 解决方案:
- 🟠 引入记忆集记录跨代引用关系
- 🟠 记忆集是用于记录从非收集区域指向收集区域的指针集合的数据结构
🟠 设计考虑:
- 🟠 如果维护所有指向新生代的老年代对象,维护成本过高
- 🟠 需要更高效的实现方式
🔴 卡表 (Card Table)
🔴 卡表是记忆集的具体实现,解决跨代引用问题的关键技术。
🟠 卡表基本结构
🟠 实现方式:
- 🟠 使用字节数组
CARD_TABLE[]
实现 - 🟠 每个元素对应内存中一块连续地址区域(卡页)
- 🟠 HotSpot使用2^9=512字节的卡页大小
🟠 工作原理:
- 🟠 如果卡页中有引用指向待回收区域,对应元素置为1(脏卡)
- 🟠 没有跨代引用则置为0
- 🟠 GC时只需扫描脏卡对应的卡页
🟡 卡表工作流程
🟡 并发标记阶段:
- 🟡 对象引用发生变化时,所在卡页被标记为脏卡
- 🟡 脏卡记录了并发标记期间的变化
🟡 重新标记阶段:
- 🟡 扫描所有脏卡,处理并发标记期间的变化
- 🟡 清除脏卡标记,为下次GC做准备
🟡 卡表的多重作用
🟡 跨代引用优化:
- 🟡 老年代识别新生代时使用卡表
- 🟡 避免全量扫描老年代
🟡 并发标记优化:
- 🟡 记录并发标记期间的对象变化
- 🟡 支持增量式垃圾回收
🟡 位图标记:
- 🟡 卡表是字节数组,每个字节有8位
- 🟡 不同位可以表示不同的引用类型和状态
🔴 Foreground CMS(前台CMS)
🔴 Foreground CMS是CMS的并发失败模式,当并发收集无法完成时触发。
🟠 并发模式失败 (Concurrent Mode Failure)
🟠 触发条件:
- 🟠 并发收集器无法在年老代填满之前完成不可达对象的回收
- 🟠 年老代中有效空闲内存空间无法满足内存分配请求
🟠 失败后果:
- 🟠 应用被暂停,进入STW状态
- 🟠 开始全局Full GC,直到回收完成
- 🟠 需要调节并发收集器参数
🟠 简单理解:并发标记时内存不够,进入STW并执行Full GC
🟡 并发失败的原因分析
🟡 内存分配速度过快:
- 🟡 应用创建对象的速度超过垃圾回收的速度
- 🟡 老年代空间被快速填满
🟡 并发收集效率不足:
- 🟡 CPU资源不足,影响并发标记效率
- 🟡 内存碎片严重,影响空间利用率
🟡 参数配置不当:
- 🟡 CMS启动阈值设置过高
- 🟡 堆内存分配不合理
⚠️ CMS重要注意事项
🔴 CMS已废弃警告
🔴 重要提醒:CMS垃圾回收器在JDK 9中被标记为废弃(deprecated),在JDK 14中被完全移除。
🔴 替代方案:
- 🔴 G1收集器:推荐用于大堆内存(>6GB)的应用
- 🔴 ZGC:超低延迟收集器,适用于超大堆内存
- 🔴 Shenandoah:低延迟收集器,与ZGC类似
🟠 CMS使用建议
🟠 适用场景:
- 🟠 对响应时间要求极高的应用
- 🟠 堆内存相对较小(<6GB)
- 🟠 CPU资源充足的环境
🟠 不适用场景:
- 🟠 大堆内存应用(推荐G1)
- 🟠 CPU资源受限的环境
- 🟠 内存碎片敏感的应用
📋 CMS面试重点问题
🔴 高频面试题
🔴 "CMS垃圾回收器的特点和工作原理是什么?"
- CMS是并发标记清除收集器,以最短回收停顿时间为目标
- 采用标记清除算法,分为4个阶段:初始标记、并发标记、重新标记、并发清除
- 并发标记和并发清除阶段与用户线程并发执行,减少停顿时间
- 缺点是会产生内存碎片,并发阶段会降低吞吐量
🔴 "CMS的优缺点是什么?"
- 优点:并发收集、低停顿、适合交互式应用
- 缺点:内存碎片、吞吐量降低、CPU敏感、浮动垃圾
🔴 "什么是并发模式失败?"
- 并发收集器无法在年老代填满前完成回收
- 年老代空间无法满足内存分配请求
- 触发时会暂停应用,执行Full GC
- 需要调节并发收集器参数
🟠 中频面试题
🟠 "CMS为什么需要扫描新生代?"
- 老年代对象可能被新生代对象引用
- 必须扫描新生代才能确定老年代对象的可达性
- 通过预处理和记忆集技术优化扫描效率
🟠 "CMS的预处理阶段有什么作用?"
- 减少重新标记阶段的工作量
- 等待Minor GC发生,减少新生代对象数量
- 通过可中止的预处理避免无限等待
🟠 "什么是记忆集和卡表?"
- 记忆集记录跨代引用关系的数据结构
- 卡表是记忆集的具体实现,使用字节数组
- 每个卡页512字节,脏卡标记有跨代引用的区域
🟡 低频面试题
🟡 "CMS有哪些重要的JVM参数?"
-XX:+CMSParallelInitialMarkEnabled
:启用并行初始标记-XX:CMSInitiatingOccupancyFraction=70
:老年代使用率70%时触发CMS-XX:+UseCMSCompactAtFullCollection
:Full GC时进行内存压缩-XX:+CMSParallelRemarkEnabled
:启用并行重新标记CMSScheduleRemarkEdenSizeThreshold
:Eden空间阈值,默认2MCMSScheduleRemarkEdenPenetration
:Eden使用率阈值,默认50%CMSMaxAbortablePrecleanTime
:预处理最大时间,默认5秒
🟡 "CMS如何处理浮动垃圾?"
- 并发清除期间产生的新垃圾称为浮动垃圾
- 浮动垃圾无法在本次回收中处理
- 留待下次垃圾回收时清理