news 2026/6/7 10:39:41

【JVM】垃圾回收器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【JVM】垃圾回收器

可以把CMS 和 G1理解成两代不同思想的垃圾回收器:

CMS 的核心目标是:尽量缩短单次 STW 时间。

G1 的核心目标是:在可控停顿时间内,尽量回收更多垃圾。


一、CMS 垃圾回收器

CMS 全称是Concurrent Mark Sweep,中文一般叫并发标记清除垃圾回收器

它主要用于老年代回收

它的设计目标是:

响应时间优先,尽量减少 STW 时间。

也就是说,CMS 更关心的是:

用户线程不要停太久。

比如一些 Web 系统、接口服务,如果一次 GC 停顿几秒,用户请求就会明显卡顿,所以 CMS 希望把 GC 的大部分工作和用户线程并发执行。


二、CMS 的回收过程

CMS 的完整过程主要有四步:

初始标记 -> 并发标记 -> 重新标记 -> 并发清理

1. 初始标记:STW

初始标记会暂停用户线程。

它只标记一小部分对象:

GC Roots 能直接关联到的对象。

比如:

线程栈中的引用 静态变量引用 常量引用 JNI 引用

这些根对象直接能找到的对象,会被先标记出来。

因为这个阶段只标记直接关联对象,所以速度很快,STW 时间较短。


2. 并发标记:不需要 STW

并发标记阶段,GC 线程和用户线程同时执行。

它会从初始标记阶段找到的对象继续往下遍历,找到所有可达对象。

比如:

GC Roots ↓ 对象 A ↓ 对象 B ↓ 对象 C

如果 A、B、C 都能从 GC Roots 间接访问到,那它们都不是垃圾。

这个阶段耗时较长,但因为它和用户线程并发执行,所以用户程序不会完全停下来。

这就是 CMS 停顿时间短的重要原因之一。


3. 重新标记:STW

问题来了。

并发标记的时候,用户线程还在运行。

用户线程可能会修改对象之间的引用关系。

比如原来是:

A -> B

并发标记过程中,用户线程改成了:

A -> C

这时候 GC 线程之前看到的对象关系可能已经过时了。

所以 CMS 需要进入重新标记阶段,再次暂停用户线程,修正并发标记期间发生变化的引用关系。

你写的“漏标”这个理解是对的,但可以说得更准确一点:

重新标记主要是为了修正并发标记期间,由于用户线程继续运行导致的标记变化,避免把仍然存活的对象误判为垃圾。

注意:GC 最怕的是把活对象当垃圾回收掉,这会导致程序错误。


4. 并发清理:不需要 STW

标记完成后,哪些对象是活的,哪些对象是垃圾,就已经知道了。

然后 CMS 会清理那些没有被标记的对象。

这个阶段也是并发执行的,GC 线程和用户线程同时工作。

但是 CMS 使用的是标记-清除算法

所以它只是把垃圾对象占用的空间释放掉,并不会移动存活对象。


三、CMS 为什么 STW 时间短?

主要有两个原因。

第一,CMS 把耗时最长的两个阶段做成了并发:

并发标记 并发清理

这两个阶段不需要长时间暂停用户线程。

第二,CMS 使用的是标记-清除算法

标记-清除不需要移动对象,只需要把垃圾对象清掉,所以清理速度相对较快。

所以 CMS 的整体特点是:

停顿时间短 响应速度好 适合低延迟系统

四、CMS 的缺点

CMS 的缺点也很明显,主要有三个。


1. 内存碎片问题

CMS 使用的是标记-清除算法

标记-清除不会整理内存。

比如老年代原来是这样的:

[对象][垃圾][对象][垃圾][对象][垃圾]

清理之后变成:

[对象][空闲][对象][空闲][对象][空闲]

虽然空闲空间很多,但这些空间是不连续的。

这就会产生内存碎片。

假设现在有很多小空闲块:

2MB + 3MB + 4MB + 5MB

总共 14MB 空间。

但是如果你要分配一个 10MB 的大对象,就可能失败,因为没有一块连续的 10MB 空间。

于是就可能触发 Full GC。


2. 浮动垃圾问题

CMS 在并发清理阶段,用户线程还在运行。

用户线程运行过程中,还可能继续产生新的垃圾。

这些垃圾是在 CMS 标记完成之后才产生的,所以本轮 CMS 没有办法处理它们。

这些垃圾就叫:

浮动垃圾。

比如:

CMS 已经完成标记 用户线程继续运行 用户线程又产生了一批垃圾

这批垃圾只能等下一次 GC 再回收。

如果浮动垃圾太多,而老年代空间又不足,就可能出现:

Concurrent Mode Failure,并发模式失败。

这时候 CMS 就撑不住了,需要退化为 Full GC。


3. 并发执行会抢 CPU

CMS 的并发标记和并发清理虽然不会完全暂停用户线程,但 GC 线程和用户线程同时运行,会抢 CPU 资源。

如果服务器 CPU 核数比较少,CMS 可能会影响用户线程的执行效率。

所以 CMS 不是完全没有代价的。


五、CMS 退化为 Serial Old 的问题

当 CMS 出现问题,比如:

老年代空间不足 浮动垃圾太多 内存碎片严重 大对象分配失败

就可能触发 Full GC。

CMS 的 Full GC 通常会退化为Serial Old

Serial Old 是单线程老年代垃圾回收器。

它会:

STW 单线程回收 使用标记-整理算法

标记-整理算法会移动对象,整理内存碎片。

比如:

[对象][空闲][对象][空闲][对象]

整理后变成:

[对象][对象][对象][空闲][空闲]

这样可以解决内存碎片问题。

但是代价是:

STW 时间非常长。

所以 CMS 的最大风险就是:

平时停顿很短,但一旦 Full GC,停顿可能非常严重。


六、G1 垃圾回收器

G1 全称是Garbage First

意思是:

优先回收垃圾最多的区域。

G1 的设计目标是:

低延迟 高吞吐 可预测停顿时间 适合大堆内存

和 CMS 不同,G1 不只是老年代回收器,它是一个面向整个堆的垃圾回收器。


七、G1 的堆内存结构

传统垃圾回收器一般把堆分成:

新生代 老年代

比如:

Eden + Survivor + Old

而 G1 把整个堆切成很多个大小相等的小块。

这些小块叫:

Region。

比如整个堆被切成这样:

Region 1 Region 2 Region 3 Region 4 Region 5 ...

每个 Region 可以动态扮演不同角色:

Eden Region Survivor Region Old Region Humongous Region

Humongous Region 用来存放大对象。

所以 G1 不是完全不要分代,而是:

逻辑上仍然有新生代、老年代,但物理上不再是连续的大块空间,而是由一个个 Region 组成。

这一点很重要。

你写的“分区取代分代”可以稍微修正成:

G1 不是取消分代,而是用 Region 作为基本管理单位,让新生代和老年代都由一组不连续的 Region 组成。


八、G1 为什么可以控制 STW 时间?

G1 有一个很重要的参数:

-XX:MaxGCPauseMillis

比如设置为:

200ms

意思是希望 GC 停顿时间尽量控制在 200ms 左右。

注意,是“尽量”,不是绝对保证。

G1 的做法是:

不一定每次都回收整个堆,而是选择部分收益最高的 Region 回收。

比如现在有这些 Region:

Region A:垃圾 80% Region B:垃圾 70% Region C:垃圾 20% Region D:垃圾 10%

如果暂停时间预算有限,G1 会优先选择:

Region A Region B

因为它们垃圾最多,回收收益最高。

这就是 Garbage First 的含义:

垃圾最多的 Region 优先回收。

所以 G1 的思路是:

有限的停顿时间内 选择最值得回收的 Region 尽量回收更多空间

九、G1 的回收过程

G1 的回收过程可以分成两类:

Young GC Mixed GC

你写的那套过程更接近 G1 的并发标记周期和混合回收过程。

整体可以这样理解:

初始标记 -> 并发标记 -> 最终标记 -> 筛选回收/混合回收

1. 初始标记:STW

初始标记也是标记 GC Roots 直接关联的对象。

这个阶段需要 STW,但时间很短。

在 G1 中,初始标记通常会借助一次 Young GC 一起完成。


2. 并发标记:不需要 STW

并发标记阶段,GC 线程和用户线程一起执行。

它会扫描整个堆,找出存活对象,并统计每个 Region 的垃圾比例。

比如:

Region A:存活对象 20%,垃圾 80% Region B:存活对象 30%,垃圾 70% Region C:存活对象 90%,垃圾 10%

这些统计信息后面会用于制定回收计划。


3. 最终标记:STW

并发标记过程中,用户线程还在运行,对象引用关系可能发生变化。

所以 G1 也需要一个最终标记阶段,修正并发标记期间的变化。

这个阶段需要 STW。


4. 筛选回收 / 混合回收:STW

并发标记结束后,G1 知道了哪些 Region 垃圾比较多。

然后它会根据停顿时间目标,选择一部分 Region 进行回收。

这一步叫Mixed GC,混合回收

为什么叫混合回收?

因为它回收的不只是新生代 Region,还可能包含部分老年代 Region。

比如:

Eden Region Survivor Region Old Region Humongous Region

G1 会选择其中一部分 Region 进行回收。


十、G1 使用什么垃圾回收算法?

G1 整体可以理解为:

局部复制算法 整体标记-整理思想

在具体回收某些 Region 时,G1 会把这些 Region 中还活着的对象复制到新的空 Region 中。

然后直接清空原来的 Region。

比如:

回收前: Region A: [活对象][垃圾][活对象][垃圾] 复制存活对象到 Region B: Region B: [活对象][活对象] 然后清空 Region A。

这样做的好处是:

不会产生 CMS 那种严重内存碎片

因为存活对象被复制到了新的连续空间,原 Region 可以整体释放。


十一、G1 相比 CMS 的优势

1. 解决内存碎片问题

CMS 用标记-清除,不移动对象,所以容易产生碎片。

G1 回收 Region 时,会复制存活对象,然后整体清空旧 Region。

所以 G1 可以减少内存碎片。


2. 可预测停顿时间

CMS 的目标是减少停顿,但不太好控制每次停顿时间。

G1 可以根据暂停时间目标选择回收哪些 Region。

所以 G1 更强调:

可预测的停顿时间。


3. 更适合大堆

CMS 在大堆场景下容易出现碎片和 Full GC 问题。

G1 把堆划分成 Region,可以按区域回收,所以更适合大堆内存场景。


十二、G1 的缺点

G1 也不是完美的。

它的缺点主要是:

实现复杂 维护 Region 信息成本高 需要 Remembered Set 记录跨 Region 引用 小堆场景下不一定比传统回收器更快

因为 G1 是按 Region 回收的,会遇到一个问题:

如果只回收某几个 Region,那怎么知道其他 Region 有没有引用这里面的对象?

比如:

Region A 里的对象 -> Region B 里的对象

如果现在只回收 Region B,那必须知道 Region A 有没有引用 Region B。

所以 G1 需要维护额外的数据结构,叫:

Remembered Set,记忆集。

它用来记录跨 Region 的引用关系。

这会带来额外的内存和性能开销。


十三、CMS 和 G1 对比

对比项CMSG1
设计目标低停顿、响应时间优先可预测停顿、兼顾吞吐
主要作用老年代回收整个堆回收
内存结构新生代 + 老年代连续划分Region 分区管理
回收算法标记-清除复制 + 标记整理思想
是否容易产生碎片容易不容易
是否支持暂停时间目标不强支持
Full GC 风险较高相对较低
适用场景低延迟系统,老版本 JVM 常用大堆、低延迟、可预测停顿系统

十四、面试时可以这样总结

你可以这样说:

CMS 是一款以响应时间优先为目标的老年代垃圾回收器,它采用标记-清除算法,主要流程包括初始标记、并发标记、重新标记和并发清理。其中并发标记和并发清理可以和用户线程同时执行,因此减少了 STW 时间。但是 CMS 存在内存碎片和浮动垃圾问题,当老年代空间不足或发生并发模式失败时,会退化为 Serial Old,触发长时间 Full GC。

然后说 G1:

G1 是一款面向整个堆的垃圾回收器,它把堆划分为多个大小相等的 Region,每个 Region 可以动态作为 Eden、Survivor、Old 或 Humongous 区。G1 可以根据用户设置的暂停时间目标,优先回收垃圾比例最高、收益最大的 Region。它的回收过程包括初始标记、并发标记、最终标记和混合回收。G1 通过复制存活对象到新的 Region,再整体清空旧 Region,减少了内存碎片问题,适合大堆内存、低延迟、可预测停顿时间的场景。


十五、你这份笔记需要改的几个点

你整体理解是对的,但有几个地方建议修正:

1. “对象消失”这个说法不太准确

你写:

可能会有些对象的引用关系变化,导致漏标(对象消失)

建议改成:

并发标记期间用户线程仍在运行,可能修改对象引用关系,导致标记结果不准确,因此需要重新标记来修正。

核心不是“对象消失”,而是引用关系变化导致标记结果需要修正。


2. “G1 不再分新生代和老年代”不够准确

建议改成:

G1 仍然保留分代思想,但物理上不再要求新生代和老年代是连续空间,而是由多个 Region 动态组成。

也就是说:

不是没有新生代和老年代 而是新生代和老年代不再是连续的大块内存

3. G1 的混合回收不是一次回收所有区域

你写得基本对,但要强调:

Mixed GC 不是把所有老年代都回收,而是选择一部分收益高的 Old Region 和整个年轻代一起回收。

也就是说,G1 是“挑着回收”。


4. G1 的暂停时间目标不是绝对保证

你写:

STW 的时间不能超过 200ms

建议改成:

G1 会尽量让 STW 时间接近或不超过设定目标,但不是绝对保证。

因为实际停顿时间受存活对象数量、堆大小、引用关系复杂度等因素影响。


一句话总结:

CMS 是“尽量并发,减少停顿”,但容易产生碎片和 Full GC;G1 是“分区管理,优先回收垃圾最多的区域”,可以更好地控制停顿时间并减少碎片。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/7 10:33:45

别只当对象存储用!用MinIO Admin命令玩转监控、调试与安全审计

解锁MinIO Admin高阶玩法:构建企业级监控、调试与安全审计体系在微服务架构中,对象存储已从简单的数据仓库升级为核心基础设施。MinIO作为高性能的开源对象存储解决方案,其Admin工具集常被低估——大多数团队仅用它执行基础运维,却…

作者头像 李华
网站建设 2026/6/7 10:33:11

5分钟掌握B站视频下载:BBDown命令行工具完全指南

5分钟掌握B站视频下载:BBDown命令行工具完全指南 【免费下载链接】BBDown Bilibili Downloader. 一个命令行式哔哩哔哩下载器. 项目地址: https://gitcode.com/gh_mirrors/bb/BBDown 在数字内容日益丰富的今天,如何高效保存Bilibili(B…

作者头像 李华
网站建设 2026/6/7 10:32:46

PHP输入输出与缓冲控制

PHP输入输出与缓冲控制输出缓冲是PHP中控制响应输出的机制。今天说说PHP的输出缓冲和各种输入输出技术。PHP的输出缓冲用ob系列函数控制。phpob_start(); echo "内容被缓冲\n"; $content ob_get_clean(); echo "输出: $content\n";ob_start(); echo "…

作者头像 李华
网站建设 2026/6/7 10:32:44

定量与定性双轨驱动的数据清洗方法论

1. 项目概述:为什么数据清洗不能只靠“删空行”和“去重”“数据清洗”这个词,在很多人的印象里,就是Excel里点几下筛选、CtrlH替换几个错别字、再把重复的客户记录手动合并一下。我带过不少刚转行做数据分析的朋友,他们第一次交来…

作者头像 李华
网站建设 2026/6/7 10:30:50

MuleSoft企业级AI编排:让大语言模型成为可审计、可治理的生产组件

1. 项目概述:当企业级集成平台遇上大语言模型,不是叠加,而是重定义“AI Orchestration in Action: How MuleSoft and LLMs Fuel the Future of Enterprise AI”——这个标题里藏着一个正在发生的、静默却剧烈的范式迁移。它说的不是“用MuleS…

作者头像 李华