java中的垃圾回收器
2026/9/24 17:45:42 网站建设 项目流程

垃圾回收器

垃圾回收集器分新生代收集器、老年代收集器、组合收集器;JDK8 默认:Parallel Scavenge + Parallel Old;JDK9 开始默认 G1;

新生代

  1. Serial(串行收集器)
    单线程,STW,标记复制。
    适用:客户端、单核小应用。
    缺点:STW 时间长。启动参数:-XX:+UseSerialGC
  2. Parallel Scavenge(PS,并行回收)
    多线程,标记复制。目标:吞吐量优先
    吞吐量 = 运行用户代码时间 / (用户代码 + GC 时间)。
    搭配老年代:Parallel Old。
    参数:-XX:+UseParallelGC
  3. ParNew
    多线程,标记复制。和 PS 算法一样,但可以和 CMS 配合(PS 不能和 CMS 搭配)。
    JDK8 以前 CMS 默认新生代收集器。
    参数:-XX:+UseParNewGC

老年代

  1. Serial Old
    单线程,标记整理。
    用途:客户端;CMS 失败时的降级备用收集器。
  2. Parallel Old
    多线程,标记整理;吞吐量优先。搭配 PS。
    JDK8 默认组合:PS + Parallel Old。
  3. CMS(Concurrent Mark Sweep,并发标记清除)低延迟,并发标记 + 清除,会产生内存碎片

阶段:初始标记 (STW) → 并发标记 → 重新标记 (STW) → 并发清除

缺点:

  • 内存碎片,Full GC 后需要整理;
  • 浮动垃圾;
  • 并发失败(Concurrent Mode Failure)触发 Full GC,退化 Serial Old;
  • 占用 CPU。
    JDK9 开始废弃 CMS。
    参数:-XX:+UseConcMarkSweepGC

整堆收集器(新生代 + 老年代一起回收)

  1. G1(Garbage-First)
    分区模型(Region),不再严格分新生代老年代;可预测停顿。
    算法:标记整理,整体无碎片;局部标记复制。
    目标:兼顾吞吐量和低延迟。
    优先回收垃圾最多的 Region。
    JDK9 默认 GC。
    参数:-XX:+UseG1GC
  2. ZGC(Z Garbage Collector)
    JDK11 引入,JDK15 正式启用。
    目标:极低延迟,STW 几乎 ms 级。
    染色指针,读屏障;几乎全并发。
    最大堆支持 TB 级。几乎没有 STW。
    JDK17 推荐生产使用。
    参数:-XX:+UseZGC
  3. Shenandoah
    RedHat 开发,和 ZGC 类似,低延迟;
    不需要移动对象时的读屏障,用转发指针。
    JDK12 引入,OpenJDK。

对比表

收集器算法特点目标缺点
Serial新生标记复制单线程简单,小内存STW 长
Parallel Scavenge新生标记复制多线程吞吐量优先停顿时间不可控
ParNew新生标记复制多线程配合 CMS已慢慢淘汰
Serial Old老年标记整理单线程兜底回收STW 很长
Parallel Old老年标记整理多线程吞吐量优先停顿不可控
CMS老年标记清除并发低延迟碎片、并发失败,JDK9 废弃
G1整堆标记整理 + 复制分区 Region可控停顿大堆场景性能不如 ZGC
ZGC整堆染色指针全并发亚毫秒级 STWCPU 消耗略高
Shenandoah整堆转发指针全并发低延迟生态不如 ZGC 成熟

垃圾回收器执行流程

CMS(Concurrent Mark Sweep,并发标记清除)

CMS 是老年代收集器,目标:低延迟,尽量并发减少 STW;算法:标记清除,会产生内存碎片。
搭配新生代:ParNew。JDK9 开始废弃。
核心:只有两个阶段 STW,其余阶段 GC 线程和业务线程并发执行

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

1. 初始标记(Initial Mark)【STW,很短】

只标记GC Roots 直接可达的对象(第一层对象),不会递归遍历整个对象图。

暂停所有用户线程。因为只扫根直接引用,速度很快。

2. 并发标记(Concurrent Mark)【并发,无 STW】

GC 线程和业务线程同时运行。
从初始标记得到的根对象开始,递归遍历整个老年代对象图,标记所有存活对象。

⚠️ 并发阶段业务线程还在修改引用,会产生漏标问题。CMS 使用增量更新方案解决漏标:
如果黑色对象新增指向白色对象的引用,写屏障把这个黑色对象记录到灰色栈,留到 Remark 阶段重新扫描。

3. 重新标记(Remark)【STW,时间波动大】

处理并发标记阶段产生的漏标对象。
扫描灰色栈里记录的对象,重新遍历它们的引用,补标记存活对象。

这个阶段 STW 时间不确定,如果并发阶段大量新增引用,灰色栈很大,STW 会明显变长。

4. 并发清除(Concurrent Sweep)【并发,无 STW】

GC 线程和业务线程并发运行。
遍历堆,把未被标记的对象当成垃圾直接回收,不移动存活对象。

算法是标记清除,不会整理内存,会产生内存碎片

G1(Garbage-First)

G1:Garbage-First,分区 Region 模型,整堆收集器,目标可预测停顿。
GC 分为两类:Young GCMixed GC;尽量避免 Full GC(Full GC 是退化,单线程 Serial Old,STW 很长)
G1 采用SATB 原始快照解决三色标记漏标。

Young GC(年轻代回收)

触发条件:Eden 区耗尽。

  1. STW,暂停业务线程
  2. 标记 Eden、Survivor 里存活对象
  3. 复制存活对象到 Survivor Region;对象年龄增加,达到阈值晋升到老年代 Region
  4. 清空 Eden

只回收年轻代 Region,不处理老年代;Mixed GC 的初始标记会依附在 Young GC 末尾,复用这次 STW,减少一次停顿

Mixed GC(混合 GC,G1 核心,老年代占比达到 IHOP 阈值触发)

一次性回收部分年轻代 + 部分垃圾最多的老年代 Region,一共 4 个阶段:

  1. 初始标记(Initial Mark)【STW】
    依附在 Young GC 之后,复用 STW。只标记 GC Roots直接可达对象。

  2. 并发标记(Concurrent Mark)【并发,业务线程继续跑】
    GC 线程和用户线程并发,从初始标记的对象遍历整个堆,标记存活对象。
    当业务线程删除 / 覆盖引用,SATB 写屏障会把旧对象压入 SATB 队列,防止漏标。

产生浮动垃圾:快照时刻存活,但并发阶段已经失效的对象,本次 GC 不能回收。

  1. 最终标记(Remark)【STW】
    短暂暂停,处理 SATB 队列剩余对象,完成全部存活对象标记。

  2. 筛选回收(Cleanup & Evacuation)【STW】
    ① 对所有 Region 按垃圾占比排序;
    ② 根据-XX:MaxGCPauseMillis目标停顿时间,挑选垃圾最多的一批 Region;
    复制存活对象到空闲 Region(Evacuation 转移)
    ④ 清空这批 Region。

因为是复制存活对象,所以不会产生内存碎片。
⚠️ Mixed GC ≠ Full GC:Mixed GC 只挑选部分老年代 Region 回收,不是全部老年代。

G1如何解决三色指针漏标问题

G1 使用SATB 写屏障解决三色标记漏标问题。SATB 的核心思想:在初始标记完成那一刻拍一个逻辑快照,凡是快照时刻存活的对象,本轮 GC 一律当成存活,不允许被漏标回收

漏标必要条件

  1. 黑色对象新增引用指向白色对象
  2. 灰色对象指向该白色对象的全部引用被删除

SATB 写屏障只拦截「引用删除 / 覆盖」操作
当业务线程执行obj.field = null或者覆盖旧引用:

  1. 判断:被删除的旧引用指向的对象,是否是快照内对象(地址 ≤ TAMS)
  2. 如果是快照内对象 → 把这个对象压入SATB 队列
  3. 如果是快照后新建对象(>TAMS)→ 不处理

重点:SATB不捕获新增引用,只捕获删除快照内对象的引用。

TAMS(Top At Mark Start)
标记开始时的 Region 分配顶指针,G1 专属,在Mixed GC 初始标记完成瞬间,给每一个 Region 记录下当时 Region 的top值。

先搞懂 Region 里的两个指针

  • bottom:Region 内存起始地址
  • top对象分配游标,新对象从 top 往后分配;top 前面是已经分配的对象,top 后面是空闲空间。

Region 内存:bottom | 已分配对象 | top | 空闲空间 | end

TAMS 的作用
初始标记完成(快照点),G1 对每个 Region 保存:TAMS = 当前Region.top
以此划分 Region 内两类对象:

  1. 对象地址 ≤ TAMS:快照前分配的对象(快照内对象),SATB 保护范围。删除这类对象的引用,写屏障会把对象压入 SATB 队列。
  2. 对象地址 > TAMS:快照之后并发标记阶段新建的对象(快照外对象),不受 SATB 保护。删除它的引用,写屏障不做任何处理。

✅ G1 判断对象是否属于快照内对象,底层就是比较对象地址和所属 Region 的 TAMS。

ZGC(Z Garbage Collector)

ZGC:低延迟并发垃圾收集器,目标亚毫秒级 STW,堆可以支持 TB 级。
核心技术:染色指针(Colored Pointers) + 读屏障(Load Barrier)
ZGC 是并发标记 + 并发转移(复制),全程几乎不 STW。
ZGC 没有分代,不区分新生代老年代;没有 Region,是 Page 页管理。
三色标记保护:读屏障,和 G1 SATB、CMS 增量更新不一样。

ZGC 一次 GC 分为 4 大阶段,只有初始标记、再标记两个极短 STW 阶段,STW 时间和堆大小无关,只和 GC Roots 数量相关。

1. 初始标记(Pause Mark Start)【STW|极短】

暂停所有业务线程。
只扫描GC Roots,标记 Root 直接可达对象。

✅ STW;只找根,不递归遍历对象图。

2. 并发标记(Concurrent Mark)【并发,业务线程正常跑】

GC 线程和业务线程并发执行。
从 Root 对象开始遍历整个对象图,标记存活对象。
读屏障发挥作用:
业务线程读取对象引用时触发读屏障,检查指针染色状态;如果对象正在被移动,读屏障会转发到新地址,保证拿到正确对象。

ZGC 靠读屏障,解决并发标记时的对象引用变化问题。

3. 再标记(Pause Mark End)【STW|极短】

短暂 STW,处理并发标记阶段遗留的少量引用,完成存活对象标记。

这是第二轮 STW,时间很短。标记阶段到此结束。

4. 并发转移(Concurrent Relocate)【并发,业务线程正常跑】

ZGC 挑选垃圾多的 Page,准备回收。
并发把存活对象复制转移到新 Page(类似复制算法)。

  • 对象复制完成后,旧对象指针保留染色标记;
  • 后续业务线程访问旧地址时,读屏障自动转发到新对象地址
  • 等所有引用都更新到新地址,旧 Page 直接释放回内存。

转移全程并发,不会 STW。

补充:并发转移完成后,还有一个并发重置(Concurrent Reset),重置 Page,为下一轮 GC 做准备,并发执行。

ZGC如何解决三色指针漏标问题

ZGC 使用「读屏障 + 染色指针(Colored Pointers)」来解决并发标记漏标,采用的是读快照(Load Barrier)方案。
ZGC 要保证:标记开始那一刻存活的对象,本轮 GC 一定全部标记,不能漏。

ZGC 核心:染色指针(Colored Pointer)
ZGC 在对象引用指针本身,借用地址高位几个 bit 存标记状态:

  • Marked0
  • Marked1
  • Remapped

不是对象头!是指针本身。
所以 ZGC 要求操作系统是64位且堆不能超过 4TB,否则地址位不够。

通过染色指针,ZGC 可以不用去对象头,直接从指针就能判断对象标记状态

ZGC 的读屏障(Load Barrier)
ZGC 没有写屏障(G1/CMS 是写屏障),ZGC 用读屏障
读屏障:每当业务代码读取对象引用的时候,触发一小段逻辑。

当线程读取引用obj.field,读屏障会检查:
如果这个指针是未标记的快照内对象(白色),立刻把这个对象标记成存活,并且递归标记它的子对象。

ZGC并发标记核心规则
ZGC 在标记开始,选定本轮标记版本(Marked0 / Marked1,二选一交替使用)。
对象在标记开始前分配:对象引用指针上没有本轮标记版本 →快照前对象,本轮需要标记保护,防止漏标
对象在标记开始之后分配:分配的时候直接把这个新对象的指针标记为本轮已标记(Marked0/Marked1)
👉新对象出生即标记为存活,本轮 GC 不会去扫描、回收它,直接交给下一轮 GC。

标记举例:

  • GC 周期 A 启动,选择本轮标记版本:Marked0
  • 标记开始前,对象 C 已经分配,指针无 Marked0 标记 →快照内对象
  • 并发标记期间,业务 new 对象 B,分配时直接打上Marked0快照后对象
  • 读屏障读到 C:C 没有 Marked0,属于快照内对象,如果白色,就标记存活
  • 读屏障读到 B:B 自带 Marked0,快照后对象,直接放行,不处理

等到下一轮 GC 周期 B,切换标记版本为Marked1。此时 B 上面的 Marked0 就不再是本轮标记,B 就变成本轮快照内对象。

怎么防止漏标
漏标发生的根源:业务删掉灰色引用,同时黑色新增指向白色快照对象,GC 看不到。
在 ZGC:
只要业务线程读取这条黑色对象指向白色对象的引用,读屏障就触发,当场把白色对象标记存活
👉 只要这条引用被业务访问一次,对象就被标记,不会丢失。

ZGC 的快照承诺:标记开始时存活的对象,本轮必须保留。
标记开始之后新创建的对象,本轮 GC 完全不处理,全部留给下一轮 GC(和 G1 不一样)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询