JVM G1 和 CMS 详解与对比

G1(Garbage-First)和 CMS(Concurrent Mark Sweep)都是 JVM 中针对老年代 的垃圾收集器,旨在解决传统 Serial Old、Parallel Old 收集器的STW(Stop-The-World)时间过长问题,适用于高并发、低延迟的应用场景。但二者的设计理念、实现机制和适用场景差异显著,本文将从核心特性、工作原理、优缺点及对比维度展开详解。

一、CMS 收集器详解

CMS 是第一款真正意义上的并发低延迟垃圾收集器 ,基于标记 - 清除(Mark-Sweep) 算法实现,核心目标是尽可能缩短 STW 时间,适用于对响应时间要求高的业务(如 Web 服务)。

1. 核心设计特点

  • 并发收集:大部分垃圾收集工作与用户线程并行执行,仅在少数阶段暂停用户线程。
  • 标记 - 清除算法 :只标记存活对象,直接清除死亡对象,不进行内存压缩
  • 老年代专属:需配合新生代的 Serial GC 或 ParNew GC 使用(通常搭配 ParNew + CMS)。

2. 工作流程(分为 6 个阶段,其中 4 个并发阶段)

(1)初始标记(Initial Mark)- STW
  • 目标 :标记GC Roots 直接关联的老年代对象(如新生代存活对象引用的老年代对象、全局静态变量引用的对象)。
  • 特点:暂停时间极短,仅扫描 GC Roots 直接关联的对象,无复杂遍历。
(2)并发标记(Concurrent Mark)- 并发
  • 目标 :从初始标记的对象出发,遍历整个老年代的对象引用图,标记所有存活对象。
  • 特点 :与用户线程并行执行,无 STW,但可能因用户线程修改对象引用产生浮动垃圾(Floating Garbage)(标记过程中产生的新垃圾,需等待下一次 GC 清理)。
(3)重新标记(Remark)- STW
  • 目标 :修正并发标记阶段因用户线程运行导致的标记遗漏(如对象引用被修改、新对象创建)。
  • 优化手段 :使用增量更新(Incremental Update) 算法,仅处理被修改的引用,缩短 STW 时间。
  • 特点:STW 时间比初始标记长,但远短于 Full GC;是 CMS 中主要的 STW 阶段之一。
(4)并发清除(Concurrent Sweep)- 并发
  • 目标:遍历老年代空间,清除所有未被标记的死亡对象,释放内存。
  • 特点 :与用户线程并行执行,无 STW;但因标记 - 清除算法,会产生内存碎片
(5)并发重置(Concurrent Reset)- 并发
  • 目标:重置 CMS 收集器的内部状态(如标记位、数据结构),为下一次 GC 做准备。

3. 关键参数

参数 作用
-XX:+UseConcMarkSweepGC 启用 CMS 收集器
-XX:+UseParNewGC 新生代使用 ParNew(与 CMS 配合的默认新生代收集器)
-XX:CMSInitiatingOccupancyFraction 设置 CMS 触发的老年代占用阈值(默认 92%,如设为 70 表示老年代用了 70% 时触发 CMS)
-XX:+CMSFullGCsBeforeCompaction 设置多少次 CMS 后执行一次内存压缩(默认 0,即每次 Full GC 都压缩)
-XX:+UseCMSCompactAtFullCollection 开启 Full GC 时的内存压缩(解决碎片问题)

4. 优缺点

优点
  • 低延迟:大部分阶段并发执行,STW 时间极短,适合对响应时间敏感的应用(如电商、金融交易系统)。
  • 成熟稳定:JDK 1.5 引入,经过长期迭代优化,在 JDK 8 中仍被广泛使用。
缺点
  • 内存碎片 :标记 - 清除算法不压缩内存,长期运行会产生大量碎片,导致分配大对象时触发 Full GC(STW 时间长)。
  • CPU 敏感:并发阶段需要占用 CPU 资源,在 CPU 核心数少的机器上(如 2 核),会与用户线程竞争 CPU,导致应用吞吐量下降。
  • 浮动垃圾 :并发标记阶段产生的垃圾无法被当前 GC 清理,需占用额外内存空间,若老年代空间不足,会触发Concurrent Mode Failure(并发模式失败),进而退化为 Serial Old GC(STW 时间更长)。
  • 仅支持老年代:需与新生代收集器配合,无法单独使用。

二、G1 收集器详解

G1(Garbage-First)是 JDK 7 引入、JDK 9 默认的垃圾收集器,设计目标是在高吞吐量的前提下,实现可预测的 STW 时间,适用于大内存(如 8GB 以上)、低延迟的应用场景。

1. 核心设计特点

  • 区域化内存管理 :将堆内存划分为多个大小相等的Region (区域,默认 1MB~32MB,可通过-XX:G1HeapRegionSize设置),新生代和老年代不再是连续的内存块,而是由多个 Region 组成(动态变化)。
  • 混合收集:同时处理新生代和老年代,无需单独的新生代收集器。
  • 标记 - 整理(Mark-Compact)+ 复制算法:在回收时,将存活对象复制到新的 Region,同时完成内存压缩,避免碎片。
  • 可预测的停顿 :用户可通过-XX:MaxGCPauseMillis(默认 200ms)设置目标 STW 时间,G1 会根据历史 GC 数据动态调整回收策略,尽量满足该目标。
  • 垃圾优先 :优先回收垃圾比例最高的 Region(即回收成本最低的 Region),以最大化回收效率。

2. 内存布局

G1 的堆内存被划分为一系列大小相同的 Region,主要包含以下类型:

  • Eden Region:新生代伊甸区,对象创建的主要区域。
  • Survivor Region:新生代幸存区,存储每次 GC 后存活的新生代对象。
  • Old Region:老年代区域,存储存活时间较长的对象。
  • Humongous Region :存储大对象(大小超过一个 Region 的 50%),直接划入老年代,避免大对象在新生代和老年代之间频繁移动。

3. 工作流程(分为 5 个阶段,含并发和 STW 阶段)

(1)初始标记(Initial Mark)- STW
  • 目标 :标记 GC Roots 直接关联的对象,同时标记新生代 Survivor 区引用的老年代对象
  • 触发时机:通常在新生代 GC(Young GC)时顺便执行,因此 STW 时间极短。
(2)并发标记(Concurrent Mark)- 并发
  • 目标 :从初始标记的对象出发,遍历整个堆的对象引用图,标记所有存活对象,并计算每个 Region 的垃圾比例(存活对象占比)。
  • 特点 :与用户线程并行执行,无 STW;过程中会记录对象的引用变化(使用SATB(Snapshot At The Beginning) 算法,解决并发标记的一致性问题)。
(3)最终标记(Final Mark)- STW
  • 目标 :处理并发标记阶段的引用更新日志,修正标记结果。
  • 特点 :使用多线程并行执行,会STW ,但时间较短。
(4)筛选回收(Live Data Counting and Evacuation)- STW
  • 目标
    1. 计算每个 Region 的垃圾比例,排序后选择垃圾比例最高的 Region(优先回收)。
    2. 将选中的 Region 中的存活对象复制到新的 Region(Eden/Survivor/Old),同时清除原 Region 的垃圾,完成内存压缩。
  • 特点 :可通过-XX:MaxGCPauseMillis控制回收的 Region 数量,从而控制 STW 时间;是 G1 中主要的 STW 阶段,但时间可预测。
(5)新生代收集(Young GC)
  • G1 的 Young GC 独立于混合收集,当 Eden Region 满时触发,将存活对象复制到 Survivor Region 或 Old Region,STW 时间短且可预测。

4. 关键参数

参数 作用
-XX:+UseG1GC 启用 G1 收集器
-XX:MaxGCPauseMillis 设置目标 STW 时间(默认 200ms,G1 会尽量满足)
-XX:G1HeapRegionSize 设置 Region 大小(1MB~32MB,需为 2 的幂)
-XX:G1NewSizePercent 新生代最小占比(默认 5%)
-XX:G1MaxNewSizePercent 新生代最大占比(默认 60%)
-XX:InitiatingHeapOccupancyPercent 触发并发标记的堆占用阈值(默认 45%)

5. 优缺点

优点
  • 可预测的 STW 时间:通过控制回收的 Region 数量,能满足用户设定的最大停顿时间,适合低延迟场景。
  • 无内存碎片:采用复制 + 标记 - 整理算法,回收时完成内存压缩,避免碎片问题。
  • 大内存友好:区域化管理使 G1 在大内存(如 16GB、32GB)下的 GC 效率远高于 CMS。
  • 统一管理新生代和老年代:无需配合其他收集器,简化配置。
  • 垃圾优先回收:优先回收垃圾多的 Region,回收效率更高。
缺点
  • 内存开销高:G1 需要维护每个 Region 的元数据(如垃圾比例、存活对象数),以及 SATB 算法的引用日志,内存开销约为堆内存的 10%~20%,比 CMS 高。
  • CPU 消耗大:并发标记和筛选回收阶段的线程调度、数据统计等操作会消耗更多 CPU 资源。
  • 小内存场景优势不明显:在小内存(如 4GB 以下)场景下,G1 的开销可能超过其优势,性能不如 Parallel GC 或 CMS。

三、G1 与 CMS 的核心对比

对比维度 CMS G1
设计目标 尽可能缩短 STW 时间,追求低延迟 可预测的 STW 时间,兼顾吞吐量和低延迟
内存管理 新生代和老年代连续内存块,固定分区 堆划分为多个 Region,动态分区(新生代 / 老年代由 Region 组成)
垃圾回收算法 标记 - 清除(Mark-Sweep) 标记 - 整理(Mark-Compact)+ 复制算法
内存碎片 严重(标记 - 清除不压缩) 无(回收时复制存活对象,完成压缩)
收集范围 仅老年代(需配合 ParNew) 新生代 + 老年代(统一管理)
STW 特性 STW 时间短但不可预测,可能因 Concurrent Mode Failure 导致长时间 STW STW 时间可通过参数设定,G1 动态调整回收策略以满足目标
大对象处理 直接在老年代分配,易产生碎片 用 Humongous Region 存储,回收时统一处理,无碎片
并发阶段 CPU 消耗 较低(仅并发标记和清除) 较高(需维护 Region 元数据、SATB 日志等)
内存开销 较低(约堆内存的 5%) 较高(约堆内存的 10%~20%)
适用场景 小内存(4GB 以下)、低延迟、CPU 核心数少的场景 大内存(8GB 以上)、可预测延迟、高并发的场景
JDK 支持 JDK 1.5 引入,JDK 9 被标记为废弃,JDK 14 移除 JDK 7 引入,JDK 9 成为默认收集器,持续优化
浮动垃圾处理 并发阶段产生的浮动垃圾需下次 GC 清理,易触发 Concurrent Mode Failure 同样存在浮动垃圾,但 Region 化管理降低了风险

四、选型建议

  1. 选择 CMS 的场景

    • 应用部署在小内存(4GB 以下)CPU 核心数少(2~4 核) 的机器上。
    • 瞬时延迟敏感,且能接受少量内存碎片和偶尔的 Full GC。
    • 基于 JDK 8 及以下版本,且无需大内存支持。
  2. 选择 G1 的场景

    • 应用部署在大内存(8GB 以上)多核 CPU的机器上(如云服务器、物理机)。
    • 要求可预测的 STW 时间(如电商秒杀、金融交易系统)。
    • 希望避免内存碎片,减少 Full GC 的发生。
    • 使用 JDK 9 及以上版本(G1 为默认收集器,优化更完善)。
  3. 其他选择

    • 若追求高吞吐量(如后台批处理任务),可选择 Parallel GC(Parallel Scavenge + Parallel Old)。
    • 若追求极致低延迟(如微服务、实时计算),可选择 ZGC 或 Shenandoah(JDK 11 + 支持,STW 时间可达毫秒级)。

五、总结

CMS 是第一代并发低延迟收集器 ,通过标记 - 清除算法实现了短 STW 时间,但存在内存碎片、CPU 敏感、并发模式失败等问题,适合小内存、低延迟的传统应用;G1 是新一代垃圾收集器,通过区域化管理、垃圾优先回收和可预测停顿,解决了 CMS 的核心痛点,更适合大内存、高并发的现代应用。

随着 JDK 版本的迭代,G1 已成为主流选择,而 CMS 逐渐被废弃。在实际项目中,应根据内存大小、CPU 核心数、延迟要求等因素选择合适的收集器,并通过压测和监控持续优化 GC 参数。

相关推荐
期待のcode4 小时前
原子操作类LongAdder
java·开发语言
舟舟亢亢4 小时前
Java集合笔记总结
java·笔记
小酒窝.5 小时前
【多线程】多线程打印ABC
java
乡野码圣5 小时前
【RK3588 Android12】RCU机制
java·jvm·数据库
JAVA+C语言5 小时前
如何优化 Java 多主机通信的性能?
java·开发语言·php
编程彩机6 小时前
互联网大厂Java面试:从分布式架构到大数据场景解析
java·大数据·微服务·spark·kafka·分布式事务·分布式架构
m0_561359676 小时前
掌握Python魔法方法(Magic Methods)
jvm·数据库·python
小酒窝.6 小时前
【多线程】多线程打印1~100
java·多线程
君爱学习7 小时前
基于SpringBoot的选课调查系统
java
APIshop7 小时前
Java 实战:调用 item_search_tmall 按关键词搜索天猫商品
java·开发语言·数据库