写后端的胖头鱼头像
关注

JVM GC之------标记清除 与 标记整理

JVM 标记 - 清除(Mark-Sweep)、标记 - 整理(Mark-Compact)

这两个都是垃圾回收算法,

作用:识别堆中死亡对象,回收内存;

区别在回收后怎么处理存活对象。

补充:还有复制算法,一般新生代用复制,老年代常用标记清除 / 标记整理。

一、标记 - 清除 Mark-Sweep

两个阶段:标记阶段 → 清除阶段

  1. 标记:从 GC Roots 出发遍历,把所有存活对象打上标记。
  2. 清除:扫描整个堆,没有标记的对象就是垃圾,直接回收;存活对象原地不动。

示意图简述

[存活][垃圾][存活][垃圾][存活]
标记后
[存活√][垃圾×][存活√][垃圾×][存活√]
清除后
[存活√][空闲][存活√][空闲][存活√]

✅优点

  • 不需要移动对象,开销小;
  • 不会损失内存,适合大量存活对象的场景(老年代)。

❌缺点

  1. 内存碎片:回收后空闲内存是零散的。当要分配大对象时,找不到连续空间,提前触发 GC。
  2. 两次扫描堆:标记扫一次,清除再扫一次,堆越大耗时越长。

应用:CMS 收集器(Concurrent Mark Sweep)就是标记清除。


二、标记 - 整理 Mark-Compact

标记阶段和标记清除完全一样,区别在回收之后

  1. 标记:GC Roots 遍历,标记存活对象。
  2. 整理(压缩):不是直接清垃圾。把所有存活对象向堆的一端移动、紧凑排列,全部挤在一起,然后一次性清理掉边界以外的全部垃圾。
[存活][垃圾][存活][垃圾][存活]
标记后
[存活√][垃圾×][存活√][垃圾×][存活√]
整理压缩后
[存活√][存活√][存活√][空闲空闲空闲]

✅优点

  1. 无内存碎片,空闲内存是一整块连续空间,分配大对象很友好;
  2. 内存分配简单,用指针碰撞(Bump The Pointer)即可。

❌缺点

  1. 需要移动对象:移动对象还要更新所有引用地址,STW(Stop-The-World)时间更长,开销大。
  2. 移动对象是全堆操作,堆越大,整理越慢。

应用: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 四个阶段:

  1. 初始标记(STW):只标记 GC Roots 直接引用对象,很快
  2. 并发标记:和用户线程一起,遍历整个对象图(耗时最长,无 STW)
  3. 重新标记(STW):修正并发标记期间用户线程修改产生的漏标对象
  4. 并发清除:和用户线程并发,清理垃圾,不移动对象

CMS 缺点

  1. 标记清除 → 内存碎片,碎片多了会触发 Full GC,此时会用 Serial Old 做标记整理压缩
  2. 并发阶段占用 CPU,多核机器才划算
  3. 浮动垃圾:并发清理时新产生的垃圾,本次 GC 无法回收,只能留到下一次
  4. 不能等到老年代满了再 GC,要预留内存给并发阶段使用

重点:CMS 不压缩对象,所以才会碎片;如果要压缩就必须移动对象,移动对象无法并发,必须 STW。

4. G1(Garbage-First)

JDK9 默认收集器,区域化(Region),不再严格区分新生代老年代,逻辑上仍有年轻区、老年代区。

  • 整体思想:标记整理,局部(Region 之间)使用复制算法
  • 算法本质:
    • 全局:标记整理(不会产生内存碎片)
    • Region 内部回收存活对象时:复制到空闲 Region
  • 工作流程:
    1. 初始标记(STW)
    2. 并发标记
    3. 最终标记(STW)
    4. 筛选回收(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 的核心差异?

  1. CMS:整个老年代统一扫描回收;G1:拆成 Region,优先回收垃圾最多区域
  2. CMS:标记清除,产生碎片;G1:标记整理,无碎片
  3. CMS:只能调并发阈值;G1 支持设置期望最大停顿时间,可预测

Q5:G1 和 ZGC 的区别?

  • G1 的筛选回收阶段是 STW,停顿时间随堆变大缓慢上涨;
  • ZGC 对象转移并发,STW 固定很小,堆多大影响不大,适合 TB 级大堆,超低延迟场景。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/Evie_Wang/article/details/166255759

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--