CMS 和 G1 垃圾回收器的区别是什么?
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
-
考察对主流垃圾回收器的理解
面试官想确认你是否清楚 CMS 和 G1 这两个 “低延迟” 回收器的基本定位,而不仅仅是背诵概念。 -
考察设计理念与原理的对比能力
更核心的是,你是否能从堆内存布局、回收算法、停顿模型等底层机制上对比它们的差异,这能反映出你对 JVM 内存管理的理解深度。 -
考察版本演进意识与生产选型经验
比如 CMS 在 JDK 9 后被标记为废弃,G1 成为默认回收器,面试官希望看到你对 JDK 版本变化的关注,以及在实际项目中如何根据业务场景选择合适的回收器。 -
考察调优实践与避坑能力
通过询问常见误区和最佳实践,面试官可以判断你是否真的用这些回收器解决过线上问题,比如 G1 的停顿时间设置、CMS 的碎片化处理等。
核心答案
CMS(Concurrent Mark Sweep)和 G1(Garbage First)都是追求低停顿时间的垃圾回收器,但设计思路截然不同。
- CMS 主要针对老年代,通过并发标记和并发清除来减少应用暂停,但它使用标记 - 清除算法,会产生内存碎片,且并发失败时会导致长时间的 Full GC。
- G1 则是一种区域化、分代化的回收器,它将堆划分为多个大小相等的 Region,既可以管理新生代也可以管理老年代,并能通过维护优先列表来预测停顿时间。G1 整体采用标记 - 整理(复制)算法,避免了碎片问题。
简单说,G1 是 CMS 的接班人,它解决了 CMS 的碎片化和不可控停顿问题,在 JDK 9 及之后版本中已成为默认回收器。
深度解析
原理 / 机制
CMS 的工作流程(主要针对老年代)
- 初始标记(Stop The World):标记 GC Roots 直接关联的对象,速度很快。
- 并发标记:从已标记对象出发,遍历整个老年代,与用户线程并发执行。
- 重新标记(Stop The World):修正并发标记期间因用户线程运行而变动的对象标记,停顿比初始标记稍长。
- 并发清除:清除标记为死亡的对象,与用户线程并发执行。
CMS 的缺点在于并发清除阶段会产生浮动垃圾,且由于标记 - 清除算法,老年代会变得碎片化。如果老年代空间不足,会触发 Concurrent Mode Failure,此时退化为 Serial Old 进行单线程 Full GC,停顿时间极长。
G1 的工作流程
G1 将堆划分为多个大小相同的 Region(通常 1 MB ~ 32 MB),每个 Region 可以扮演 Eden、Survivor 或 Old 区。它通过一个全局并发标记来跟踪各个 Region 的存活对象信息,并维护一个优先级列表,优先回收垃圾最多的 Region(即 “Garbage First”)。
- 初始标记(Stop The World):标记 GC Roots 直接可达的对象,并修改 TAMS(Top at Mark Start)指针,保证并发标记阶段新分配的对象不会被误标。
- 并发标记:从 GC Roots 开始对堆中所有对象进行可达性分析,并记录每个 Region 的存活对象信息,该阶段与用户线程并发。
- 最终标记(Stop The World):处理并发标记阶段遗留下来的 SATB(Snapshot-At-The-Beginning)缓冲区,修正标记。
- 筛选回收(Stop The World,但可并行):根据用户设定的停顿时间目标(-XX:MaxGCPauseMillis),计算出收益最高的 Region 集合进行回收,将存活对象复制到新的 Region 中(即标记 - 整理),然后清空旧 Region。
G1 通过这种方式,既实现了分代收集,又避免了全堆范围的碎片化,并且可以控制每次 GC 的停顿时间。
对比分析
| 维度 | CMS | G1 |
|---|---|---|
| 堆内存布局 | 传统分代:新生代(Eden+Survivor)、老年代物理隔离 | Region 分区:逻辑分代但物理不连续,每个 Region 独立 |
| 回收算法 | 老年代:标记 - 清除(会产生碎片) | 整体采用标记 - 整理(复制),无内存碎片 |
| 停顿时间 | 追求低停顿,但不可预测;并发失败时停顿极长 | 可预测停顿,通过 -XX:MaxGCPauseMillis 指定目标,但可能牺牲吞吐量 |
| 并发阶段 | 并发标记和并发清除,但并发标记耗时较长 | 并发标记是全局的,之后进行筛选回收,可控制停顿 |
| 适用场景 | 适合中等堆内存(如 4-6 GB)、重视响应时间、可以接受碎片化风险的场景 | 适合大堆(6 GB 以上)、多核 CPU、要求可预测停顿、需要避免 Full GC 的场景 |
| 版本状态 | JDK 9 起标记为废弃,JDK 14 正式移除 | JDK 9 起成为默认垃圾回收器,持续优化 |
最佳实践
- 新项目直接使用 G1,并根据业务设置合理的停顿时间(例如 -XX:MaxGCPauseMillis=200)。如果堆内存较小(< 4 GB)或对吞吐量要求极高,Parallel Scavenge 可能更合适。
- 监控 GC 日志,观察 G1 的 Young GC、Mixed GC 频率和停顿时间,适时调整 Region 大小(-XX:G1HeapRegionSize)或并发线程数(-XX:ConcGCThreads)。
- 如果历史遗留系统仍使用 CMS(如 JDK 8),需关注老年代碎片化,可以通过 -XX:CMSFullGCsBeforeCompaction=1 在若干次 CMS GC 后进行一次压缩整理,或启用 -XX:+UseCMSCompactAtFullCollection。
- 避免设置过于激进的停顿目标(如 50 ms),否则 G1 会频繁进行部分回收,反而降低吞吐量。
常见误区
- 误区一:认为 G1 可以完全消除停顿。
实际上 G1 只是让停顿变得可预测,初始标记、最终标记和筛选回收阶段仍然需要 Stop The World。 - 误区二:在 JDK 8 中不加调优就使用 G1 并期望高性能。
JDK 8 的 G1 相对较新,默认参数可能不适合所有场景,需要根据应用特点调整;JDK 9+ 的 G1 才足够成熟。 - 误区三:混淆 CMS 的并发模式和 G1 的并发周期。
CMS 的并发阶段只针对老年代,而 G1 的并发标记是全局的,覆盖所有 Region。 - 误区四:以为 CMS 完全不需要配置就能低延迟。
CMS 对内存大小、碎片化都很敏感,如果不进行适当配置(如触发阈值、压缩),很容易出现 Concurrent Mode Failure,导致性能雪崩。
总结
CMS 和 G1 都是为低延迟设计的垃圾回收器,但 G1 通过 Region 化布局和可预测停顿机制,解决了 CMS 的碎片化和并发失败问题,更适合现代大内存、多核环境。理解两者的区别,能帮助我们在性能调优时做出更合理的选择,避免踩坑。