jvm垃圾回收器 - CMS-已弃用的垃圾回收器

CMS背景

CMS曾是为低延迟应用设计的垃圾回收器标杆,而G1则是为解决CMS设计局限并面向未来硬件诞生的继任者。它被替换和移除,是Java技术演进的必然结果。

从JDK 9开始,G1已成为JVM的默认垃圾收集器。从JDK14开始相关代码已被删除。但我们也需要了解它的原理以及为何会被替代,以便我们更清晰了解垃圾回收器的技术演进之路

🛠️ CMS垃圾回收器详解:为低延迟而生

CMS全称是Concurrent Mark Sweep,它是一款针对"低延迟"需求设计的并发垃圾收集器。传统的垃圾回收器在执行时,会暂停所有应用线程,即"Stop-The-World"(STW),导致服务响应变慢。CMS的核心思想,就是通过多线程与应用程序并发执行,来大幅缩短甚至消除这种暂停。

CMS 工作流程详解

CMS(Concurrent Mark Sweep)收集器针对老年代,目标是最大化缩短应用暂停时间。其工作流程可拆解为 4 个主要阶段 (外加两个可选预处理阶段)。整体流程是标记-清除算法的并发化实现。

阶段概览

阶段 动作 是否STW 关键特征
1. 初始标记 标记GC Roots直接引用的对象 (短暂暂停) 仅扫描根对象,速度极快,暂停时间很短
2. 并发标记 从根对象出发遍历对象图 耗时最长,与应用线程并发执行,,因此不产生STW
3. 并发预清理 提前处理部分并发标记期间的变化 可选阶段,减少后续重新标记暂停时间
4. 重新标记 修正并发标记期间因用户程序导致的对象引用变化 (暂停) 使用SATB或增量更新算法,停顿比初始标记长但可控。暂停时间通常比初始标记长,但仍远小于并发标记。
5. 并发清理 回收被标记为垃圾的对象内存 不整理内存,可能产生碎片。清理过程与用户线程同时进行,不阻塞应用。

注:实际JVM实现中还包含"并发中止预清理""最终清理"等子阶段,但核心逻辑如上。


各阶段详细说明

1. 初始标记 (Initial Mark) -- STW
  • 任务 :标记所有 GC Roots 能直接引用的对象(例如栈帧中的本地变量、静态变量、JNI引用等)。
  • 停顿时间:非常短,仅依赖根对象的数量,与堆大小无关。
  • 产出:创建了根对象标记集合,为后续并发遍历提供起点。
2. 并发标记 (Concurrent Mark) -- 与应用线程并发
  • 任务 :从初始标记的根对象集合出发,递归遍历整个老年代对象图,标记所有存活对象。
  • 特点
    • 这个阶段耗时最长,但因为是并发的,应用线程仍在运行,GC线程仅占用部分CPU。
    • 并发期间,应用线程可能修改对象引用关系(如修改指针、新建对象),导致已经标记的对象状态发生变化。
  • 处理并发变化:CMS 采用"增量更新"算法记录并发标记期间引用变更的点,留待重新标记阶段修正。
3. 并发预清理 (Concurrent Preclean) -- 并发(可选但有价值)
  • 任务 :这是一个 辅助阶段,并非严格必须。它会尝试提前处理在并发标记阶段通过增量更新记录下来的那些"脏"对象卡表,减少后续重新标记阶段的扫描工作量。
  • 目的:因为重新标记需要STW,做一次提前并发清理,可以缩短真正的STW时间。
  • 时机:当并发标记阶段基本完成,但还未进入重新标记时执行。
4. 重新标记 (Remark) -- STW
  • 任务:修正并发标记期间因应用线程运行而变化的那些对象的标记状态。
  • 采用技术 :CMS使用 增量更新 算法。在并发阶段,当应用线程修改一个已经被标记过的对象引用时,JVM会记录该对象(将其放入一个队列)。重新标记时,只扫描这些"脏对象"及其直接引用链,而不是整个堆。
  • 停顿时间:通常比初始标记长,但远小于一次Full GC。如果并发预清理阶段效果显著,停顿会很短。
  • 额外处理:还会处理新生代晋升对象、跨代引用等(需要扫描新生代或使用卡表)。
5. 并发清理 (Concurrent Sweep) -- 并发
  • 任务:回收所有在标记阶段被判定为不可达的"垃圾"对象所占用的内存空间。
  • 并发特点:清理线程与应用线程同时进行,不阻塞应用。
  • 内存布局结果 :由于是"清除"而非"复制/整理",会导致老年代出现内存碎片。这是CMS被G1取代的重要原因之一。
6. 并发重置 (Concurrent Reset) -- 并发
  • 任务:清理CMS内部数据结构,为下一次回收做准备。

特殊情况:Concurrent Mode Failure

如果并发回收的速度跟不上对象分配的速度(比如老年代急速增长),CMS可能遇到 并发模式失败。此时JVM会采取降级措施:

  • 暂停所有应用线程
  • 使用单线程的Serial Old收集器 进行一次全停顿的老年代整理(标记-整理算法)。
  • 这次Full GC通常停顿时间极长,严重损害系统性能。

总结:CMS的完整流程图示

text 复制代码
[初始标记] --STW--> [并发标记] --> [并发预清理] --> [重新标记] --STW--> [并发清理] --> [并发重置]
  短暂停         并发长耗时          并发             中短暂停          并发            并发

光环下的阴影:CMS的三大核心缺陷

尽管CMS极大地改善了GC暂停问题,但随着硬件和应用的进化,其缺点也变得愈发突出:

  • 内存碎片化 :CMS基于Mark-Sweep算法,不进行内存整理,会逐渐产生无法被利用的内存"碎片"。这可能导致明明总内存足够,却因找不到连续 空间分配给大对象而频繁触发Full GC,最终引发内存溢出错误OutOfMemoryError
  • Concurrent Mode Failure :CMS在并发回收时,应用程序会继续创建对象。如果并发期间预留内存不足,回收速度跟不上分配速度,就会发生"并发模式失败"。作为后备方案,JVM会暂停所有线程,启用单线程的Serial Old进行全局整理,此时GC停顿时间将会骤增。
  • 对CPU资源敏感:虽然并发阶段不暂停应用线程,但GC线程会消耗宝贵的CPU资源(CMS默认启动(CPU核心数+3)/4个线程)。在核心数较少的环境下,可能导致应用吞吐量显著下降。

简单说:CMS为了低延迟牺牲了稳定性和可预测性,而G1(及后续ZGC)通过分区、整理和停顿预测模型,在保证低延迟的同时彻底规避了这两个问题

🤔 为何被G1取代:继任者的全面超越

G1(Garbage-First)在设计之初就被官方定位为CMS的长期替代者。它通过全新的设计思想,从根本上解决了CMS的老大难问题:

  • 变革性的Region分区设计:G1将堆内存划分为多个大小相等的独立区域(Region),且每个Region可以动态扮演Eden、Survivor或Old区中的任意角色。
  • 消灭内存碎片 :G1从整体上采用Mark-Compact(标记-整理)算法,局部采用Mark-Copy(标记-复制)。其在回收Region时会执行"疏散"(Evacuation),将存活对象复制到新的空闲Region,从而保证内存始终是紧密排列、无碎片的。
  • 可控、可预测的停顿 :G1不追求每次都回收全部内存,而是建立一个"停顿预测模型"。用户可以设定期望的最大GC暂停时间(例如 -XX:MaxGCPauseMillis=200),G1会优先选择垃圾最多、清理收益最大的Region进行回收,在满足此目标的前提下实现吞吐量最大化。

💎 移除时间线:从废弃到告别

CMS的退场是一个循序渐进的过程:

  • JDK 9 :CMS被正式标记为废弃(Deprecated),表明官方不再推荐使用,并计划在未来版本中移除。从JDK 9开始,G1已成为JVM的默认垃圾收集器。
  • JDK 14 :Oracle通过 JEP 363 彻底移除了CMS垃圾收集器的源代码和相关选项。从该版本开始,在JVM参数中指定-XX:+UseConcMarkSweepGC将会被JVM直接忽略,并回退到使用默认的G1。
相关推荐
light blue bird9 小时前
主子表单控件图表带拖拽式控件工序管理组件
开发语言·jvm·信息可视化·桌面端winform
QWQ___qwq13 小时前
Text2SQL 完整流程模拟详细笔记
jvm·笔记·oracle
light blue bird13 小时前
可更新组装工序资源图表功能组件
开发语言·前端·jvm·.net·状态模式
light blue bird13 小时前
组装工序资源功能工序路径菜单组件
jvm·数据库·winform·gdi+界面·多节点端
菜鸟是大神14 小时前
JDK21-Windows安装
java·jvm·windows
Lei活在当下1 天前
先用起来,再理解,关于协程Coroutine应该知道的事
android·java·jvm
KobeSacre1 天前
JVM 总结
jvm
墨雪不会编程2 天前
C++ 进阶:虚函数与多态原理
java·jvm·c++
顺风尿一寸2 天前
从 FileOutputStream.write(byte) 到磁盘扇区:一次 Java 写入操作的完整内核穿越之旅
jvm