JVM 标记 - 清除(Mark-Sweep)、标记 - 整理(Mark-Compact)
这两个都是垃圾回收算法,
作用:识别堆中死亡对象,回收内存;
区别在回收后怎么处理存活对象。
补充:还有复制算法,一般新生代用复制,老年代常用标记清除 / 标记整理。
一、标记 - 清除 Mark-Sweep
两个阶段:标记阶段 → 清除阶段
- 标记:从 GC Roots 出发遍历,把所有存活对象打上标记。
- 清除:扫描整个堆,没有标记的对象就是垃圾,直接回收;存活对象原地不动。
示意图简述
[存活][垃圾][存活][垃圾][存活]
标记后
[存活√][垃圾×][存活√][垃圾×][存活√]
清除后
[存活√][空闲][存活√][空闲][存活√]
✅优点
- 不需要移动对象,开销小;
- 不会损失内存,适合大量存活对象的场景(老年代)。
❌缺点
- 内存碎片:回收后空闲内存是零散的。当要分配大对象时,找不到连续空间,提前触发 GC。
- 两次扫描堆:标记扫一次,清除再扫一次,堆越大耗时越长。
应用:CMS 收集器(Concurrent Mark Sweep)就是标记清除。
二、标记 - 整理 Mark-Compact
标记阶段和标记清除完全一样,区别在回收之后
- 标记:GC Roots 遍历,标记存活对象。
- 整理(压缩):不是直接清垃圾。把所有存活对象向堆的一端移动、紧凑排列,全部挤在一起,然后一次性清理掉边界以外的全部垃圾。
[存活][垃圾][存活][垃圾][存活]
标记后
[存活√][垃圾×][存活√][垃圾×][存活√]
整理压缩后
[存活√][存活√][存活√][空闲空闲空闲]
✅优点
- 无内存碎片,空闲内存是一整块连续空间,分配大对象很友好;
- 内存分配简单,用指针碰撞(Bump The Pointer)即可。
❌缺点
- 需要移动对象:移动对象还要更新所有引用地址,STW(Stop-The-World)时间更长,开销大。
- 移动对象是全堆操作,堆越大,整理越慢。
应用:G1 的部分场景、Serial Old、Parallel Old 老年代收集器是标记整理。
三、核心对比表
| 算法 | 步骤 | 是否移动对象 | 内存碎片 | STW | 适用场景 |
|---|---|---|---|---|---|
| 标记 - 清除 | 标记 → 清除 | ❌不移动 | 产生大量碎片 | 相对短 | 老年代,存活对象多,追求低 STW(CMS) |
| 标记 - 整理 | 标记 → 移动压缩 → 清理 | ✅移动存活对象 | 无碎片 | 较长 | 老年代,希望避免碎片 |
四、面试高频问题
1. CMS 为什么不用标记整理?
CMS 主打并发低停顿,移动对象必须暂停所有用户线程(STW),代价极高,所以 CMS 选用标记清除,牺牲碎片换低 STW;碎片问题到阈值触发 Full GC。
2. 标记整理移动对象,引用怎么处理?
移动完存活对象后,更新所有指向这些对象的引用地址,让引用指向新位置。这个过程是 STW 里完成。
3. 复制算法和标记整理区别
复制:把存活对象复制到另一块空白内存,原区域全部清空;需要两块内存,浪费一半空间,适合新生代(存活对象少)。
标记整理:在同一块内存原地压缩,不需要额外内存。
五、CMS / G1 / ZGC 收集器
总览:
新生代:复制算法为主;
老年代:标记清除 / 标记整理;
收集器不是单一算法,很多是组合算法(新生代一套,老年代一套)。
1. Serial(串行收集器)
- 新生代:复制算法
- 老年代:Serial Old(Serial 收集器的老年代版本),标记 - 整理
- 特点:单线程收集,全程 STW;收集时用户线程全部暂停。
- 使用场景:客户端、内存很小的程序。
- 面试点:最古老收集器,单线程,STW 长。
2. Parallel(并行收集器)
Parallel Scavenge + Parallel Old
- 新生代 Parallel Scavenge:复制算法
- 老年代 Parallel Old:标记 - 整理
- 特点:多线程 GC,用户线程暂停;目标是可控吞吐量(吞吐量 = 运行代码时间 / 总时间)
- 关注点:优先保证吞吐量,而不是单次停顿时间。
- 面试:吞吐量优先收集器,适合后台计算型任务。
3. CMS(Concurrent Mark Sweep,并发标记清除)
- 新生代:复制算法
- 老年代:标记 - 清除算法
- 目标:低停顿,尽量和用户线程并发执行。
CMS 四个阶段:
- 初始标记(STW):只标记 GC Roots 直接引用对象,很快
- 并发标记:和用户线程一起,遍历整个对象图(耗时最长,无 STW)
- 重新标记(STW):修正并发标记期间用户线程修改产生的漏标对象
- 并发清除:和用户线程并发,清理垃圾,不移动对象
CMS 缺点
- 标记清除 → 内存碎片,碎片多了会触发 Full GC,此时会用 Serial Old 做标记整理压缩
- 并发阶段占用 CPU,多核机器才划算
- 浮动垃圾:并发清理时新产生的垃圾,本次 GC 无法回收,只能留到下一次
- 不能等到老年代满了再 GC,要预留内存给并发阶段使用
重点:CMS 不压缩对象,所以才会碎片;如果要压缩就必须移动对象,移动对象无法并发,必须 STW。
4. G1(Garbage-First)
JDK9 默认收集器,区域化(Region),不再严格区分新生代老年代,逻辑上仍有年轻区、老年代区。
- 整体思想:标记整理,局部(Region 之间)使用复制算法
- 算法本质:
- 全局:标记整理(不会产生内存碎片)
- Region 内部回收存活对象时:复制到空闲 Region
- 工作流程:
- 初始标记(STW)
- 并发标记
- 最终标记(STW)
- 筛选回收(STW,挑选垃圾最多的 Region 优先回收,Garbage-First 名字来源)
- 特点:可预测停顿模型,用户可指定期望最大停顿时间。
和 CMS 区别:CMS 是整个老年代一起 GC;G1 只选垃圾最多的 Region,增量回收。
G1 回收后存活对象复制到别的 Region,天然整理,无碎片。
5. ZGC(Z Garbage Collector,JDK11+)
- 算法:标记 - 整理
- 核心目标:超低 STW,STW 时间几乎和堆大小无关,TB 级堆也可以做到毫秒级停顿
- 核心技术:读屏障 + 染色指针(Colored Pointer)
ZGC 几乎所有阶段都并发:标记、转移(移动对象)、重定位,只有短暂 STW。 移动对象的时候,引用还没来得及更新,靠染色指针记录对象新地址,访问时读屏障做转发。
- 特点:无内存碎片,支持超大堆,低延迟。
6. Shenandoah(JDK12+)
- 算法:标记整理
- 和 ZGC 思路接近:并发压缩,读屏障,低延迟
- 区别:Shenandoah 使用转发指针,ZGC 使用染色指针。
汇总表
| 收集器 | 新生代算法 | 老年代算法 | 特点 |
|---|---|---|---|
| Serial + Serial Old | 复制 | 标记 - 整理 | 单线程,STW 长 |
| Parallel Scavenge + Parallel Old | 复制 | 标记 - 整理 | 多线程,吞吐量优先 |
| CMS | 复制 | 标记 - 清除 | 并发低停顿,有碎片 |
| G1 | 局部复制,全局标记整理 | 标记整理 | Region 分区,可预测停顿,无碎片 |
| ZGC | 标记整理 | 标记整理 | 并发移动对象,毫秒级 STW |
| Shenandoah | 标记整理 | 标记整理 | 并发压缩,转发指针 |
面试高频连环题
Q1:CMS 为什么不用标记整理?
标记整理需要移动对象,移动对象必须更新所有引用,这个过程没法并发,会产生很长 STW。CMS 的设计目标就是低停顿,所以选择标记清除,牺牲碎片。碎片严重时,会退化到 Serial Old 做 Full GC 压缩。
Q2:G1 是标记整理吗?为什么又说用到复制算法?
G1全局是标记整理,保证没有碎片;回收单个 Region 的时候,把存活对象复制到别的空闲 Region,这个局部动作就是复制。本质是通过复制来完成整理。
Q3:ZGC 标记整理,移动对象为什么几乎没有 STW?
ZGC 是并发转移。移动对象这个动作不需要暂停用户线程。依靠染色指针 + 读屏障:对象被移走之后,旧指针不会立刻修改;用户线程访问旧对象时触发读屏障,自动转发到新地址。只有非常短暂的 STW。
Q4:CMS 和 G1 的核心差异?
- CMS:整个老年代统一扫描回收;G1:拆成 Region,优先回收垃圾最多区域
- CMS:标记清除,产生碎片;G1:标记整理,无碎片
- CMS:只能调并发阈值;G1 支持设置期望最大停顿时间,可预测
Q5:G1 和 ZGC 的区别?
- G1 的筛选回收阶段是 STW,停顿时间随堆变大缓慢上涨;
- ZGC 对象转移并发,STW 固定很小,堆多大影响不大,适合 TB 级大堆,超低延迟场景。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/Evie_Wang/article/details/166255759



