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
- 目标 :
- 计算每个 Region 的垃圾比例,排序后选择垃圾比例最高的 Region(优先回收)。
- 将选中的 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 化管理降低了风险 |
四、选型建议
-
选择 CMS 的场景:
- 应用部署在小内存(4GB 以下) 、CPU 核心数少(2~4 核) 的机器上。
- 对瞬时延迟敏感,且能接受少量内存碎片和偶尔的 Full GC。
- 基于 JDK 8 及以下版本,且无需大内存支持。
-
选择 G1 的场景:
- 应用部署在大内存(8GB 以上) 、多核 CPU的机器上(如云服务器、物理机)。
- 要求可预测的 STW 时间(如电商秒杀、金融交易系统)。
- 希望避免内存碎片,减少 Full GC 的发生。
- 使用 JDK 9 及以上版本(G1 为默认收集器,优化更完善)。
-
其他选择:
- 若追求高吞吐量(如后台批处理任务),可选择 Parallel GC(Parallel Scavenge + Parallel Old)。
- 若追求极致低延迟(如微服务、实时计算),可选择 ZGC 或 Shenandoah(JDK 11 + 支持,STW 时间可达毫秒级)。
五、总结
CMS 是第一代并发低延迟收集器 ,通过标记 - 清除算法实现了短 STW 时间,但存在内存碎片、CPU 敏感、并发模式失败等问题,适合小内存、低延迟的传统应用;G1 是新一代垃圾收集器,通过区域化管理、垃圾优先回收和可预测停顿,解决了 CMS 的核心痛点,更适合大内存、高并发的现代应用。
随着 JDK 版本的迭代,G1 已成为主流选择,而 CMS 逐渐被废弃。在实际项目中,应根据内存大小、CPU 核心数、延迟要求等因素选择合适的收集器,并通过压测和监控持续优化 GC 参数。