云计算百科
云计算领域专业知识百科平台

G1 垃圾回收器 :SATB + TAMS核心机制深度解析

前言

在 JVM 垃圾回收领域,G1(Garbage First) 凭借其可预测的停顿时间和优秀的吞吐表现,已经成为服务端应用的首选收集器之一。而支撑 G1 正确进行并发标记的核心,就是 SATB(Snapshot At The Beginning) 算法以及 TAMS(Top At Mark Start) 指针机制。

一、SATB:Snapshot At The Beginning(原始快照)

1.1 核心思想

一轮并发标记刚刚启动的瞬间,G1 会形成一张逻辑快照:凡是快照中存在的对象,本轮 GC 都必须当作存活对象处理。

通俗理解:在标记启动那一刻还活着的对象,哪怕在标记进行中彻底没人使用了,本轮 GC 也不能回收它,只能等到下一轮 GC 清理。由此产生的“可回收但本轮不回收”的对象,被称为浮动垃圾。

1.2 并发标记存在的风险(附完整时序案例)

GC 线程正在遍历对象图寻找存活节点,与此同时业务线程还在不断修改对象引用。当下面这个非常严格的时序条件同时满足时,就会出现漏标,导致存活对象被错误回收:

  • 某对象 A 原本持有对象 B 的引用,GC 线程已经遍历完成对象 A,不会再次回头扫描 A;
  • 业务线程并行执行两件事:① 切断 A 对 B 的引用(A.field = null);② 已经被 GC 遍历完毕的存活对象 C,新建引用指向 B(C.field = B)。
  • ⚠️ 关键歧义提醒:如果 C 还没有被 GC 遍历到,那么后续 GC 扫描到 C 时自然能发现 B,完全不会产生漏标!漏标成立的硬性前提是:创建新引用的对象 C,GC 已经扫描完毕,不会再被访问。

    从客观事实来看:B 依然被 C 引用,B 是存活对象。从 GC 视角来看:A 已经处理完毕不再回看;C 早已扫描完成,GC 看不到 C 新建的引用。GC 误以为 B 无任何引用,判定为垃圾,一旦回收就会触发程序崩溃。

    SATB 如何拦截这个场景(完整处理流程)

    当业务线程执行 A.field = null,切断 A→B 时:赋值前,写前屏障(Pre-Write Barrier)触发,把被丢弃的旧引用对象 B 存入 SATBMarkQueue。

    重点:屏障捕获的是被覆盖消失的对象 B,而不是新增引用的目标。

    存入队列 不等于立刻扫描:

    • 并发标记阶段,GC 不会实时处理 SATB 队列,而是继续正常遍历堆;
    • 等到 Final Mark(最终标记,短暂 STW 阶段):- GC 暂停所有业务线程;- 遍历整个 SATBMarkQueue,取出队列里所有对象(案例中的 B);- 以这些对象为起点,沿着引用链完整递归扫描、标记整条引用链上所有对象,最终保证 B 以及 B 关联的所有对象都被标记为存活,本轮 GC 绝不会回收。

    对比区分(重点修正,清晰区分新增 / 删除两种行为)- CMS 增量更新:只监控新增产生的引用;不处理单纯的引用删除。- G1 SATB:只监控被删除、被覆盖的旧引用;新增引用本身不需要屏障处理。

    补充关键区分:单纯新增引用不会引发漏标;单纯删除引用也不会引发漏标;只有「旧引用断开 + 新引用来自已经扫描完成的对象」组合在一起,才会触发漏标。SATB 通过在删除引用侧做拦截,完美打破了该组合条件。

    二、TAMS:Top At Mark Start(标记启动时的分配顶端指针)

    SATB 解决了老对象引用变更的漏标问题,但对于标记过程中新创建的对象,需要另一套高效机制来应对,这就是 TAMS。

    2.1 TAMS 数据结构

    每一个 Region 内部维护两个指针,标记阶段启动时记录当时的内存分配上限。指针顺序如下:

    bottom(Region 起始地址) → prevTAMS → nextTAMS → top(当前已分配位置) → end(Region 末尾)并发标记【刚启动一瞬间】nextTAMS = top,两者地址完全重合;
    但是标记开始之后,应用线程不断分配对象,top 持续向右移动,nextTAMS 固定不动!

    • nextTAMS(NTAMS):本轮并发标记启动瞬间,记录该 Region 当时的 top 地址;
    • prevTAMS:上一轮并发标记记录的 nextTAMS,用于前后轮标记区间判断。

    TAMS 把 Region 内存分成上下两块:

  • 地址范围 [bottom, nextTAMS)标记开始之前分配的老对象,需要 GC 逐个遍历、追踪引用链,判断是否存活。
  • 地址范围 [nextTAMS, top)并发标记过程中新分配的对象。👉 直接默认判定为存活,不需要 GC 遍历扫描!
  • 2.2 为什么需要 TAMS?

    并发标记运行期间,应用线程不断 new 新对象。如果所有这些对象都要参与完整的引用遍历,CPU 开销将巨大到不可接受。

    SATB 规则约定:标记周期内新建的对象默认全部存活。TAMS 就是这条规则在内存管理上的落地实现——一条简单的地址分界线,用以快速区分新旧对象,省去海量扫描工作。

    ⚠️ 重要区分:TAMS 只管「标记期间新建对象」,不解决上面所说的引用修改造成的漏标问题。引用修改的漏标完全由 SATB + 写前屏障负责。

    三、SATB + TAMS 完整协作时序

    整个并发标记周期中,两者协调工作如下:

  • 初始标记(Initial Mark,短暂 STW)- 遍历所有 Region,把每个 Region 当前的 top 地址赋值给 nextTAMS,刷新 TAMS 分界线;- 此时建立了快照边界。
  • 并发标记(Concurrent Marking)- 老对象 [bottom, nextTAMS) :正常遍历引用链;当旧引用被覆盖/删除时,触发写前屏障,被丢弃的对象存入 SATBMarkQueue;- 新对象 [nextTAMS, top) :依靠 TAMS 识别,直接默认存活,无需扫描。
  • 最终标记(Final Mark,短暂 STW)- 暂停所有业务线程;- 遍历清空 SATBMarkQueue,逐个取出队列内的对象;- 以每个队列对象为起点,沿着引用链完整递归标记,确保逻辑快照中的对象无一遗漏。
  • Mixed GC- 根据标记结果,挑选垃圾最多的若干 Region 执行回收,既回收年轻代也回收部分老年代。
  • 四、横向对比:SATB(G1) vs 增量更新(CMS)

    对比维度SATB(G1)增量更新(CMS)
    监控行为 旧引用被删除、覆盖 程序新建引用关系
    使用屏障 写前屏障(Pre-Write Barrier) 写后屏障(Post-Write Barrier)
    处理方式 被丢弃对象存入 SATB 队列;最终标记 STW 阶段沿引用链统一扫描标记 把产生新引用的源对象重新加入标记队列,再次遍历
    产生结果 产生浮动垃圾(本轮不回收) 无浮动垃圾(重新标记可修正)
    配套组件 TAMS 区分标记期间新建对象 不存在 TAMS(新生代由 Young GC 负责)

    五、面试精简背诵版

    SATB 是 G1 并发标记采用的算法。并发标记启动瞬间生成逻辑快照,快照内存在的对象本轮视作存活;当旧引用被覆盖/删除,写前屏障捕获被丢弃的引用存入 SATBMarkQueue,等到最终标记阶段 STW,遍历队列,以队列对象为起点沿着引用链完整标记,防止存活对象被错误回收,代价是产生浮动垃圾。

    TAMS 是每个 Region 内部的地址指针,初始标记阶段记录标记启动时刻的分配位置;TAMS 分界线之后是标记周期中新创建的对象,直接默认存活,不用遍历标记。

    协作关系:SATB 负责处理老对象引用变更的漏标问题;TAMS 用来区分标记期间新创建对象;二者配合,保障并发标记的正确性。

    六、两个深度追问

    追问一:为什么 SATB 不能区分“临时断开”与“永久删除”?

    你可能会想:既然对象 B 的引用断开了,为什么 SATB 不进一步判断 B 是否真的变成了垃圾?

    原因在于并发场景下无法零成本地精确区分。引用断开的那一刻,JVM 根本无法预知未来 0.1ms 内用户线程是否会重新建立引用。

    • 若选择“冒进回收”,一旦引用恢复就会导致 JVM 崩溃;
    • 若选择“实时扫描全堆判断”,则会严重拖慢并发标记进度,停顿时间不可控。

    因此 SATB 采用保守策略——宁可放过,不可错杀。这是并发 GC 中典型的性能与安全权衡。

    追问二:CMS 为什么没有 TAMS 这样的机制?

    CMS 没有类似 TAMS 的机制,根本原因在于分代设计差异:

    • CMS 仅负责老年代的回收,并发标记时新创建的对象全部分配在新生代(Young Gen);
    • 新生代对象完全由独立的 Young GC 负责,CMS 根本不用插手,自然不需要在 Region 内划分标记边界。

    而 G1 的 Region 可以灵活划作新生代或老年代,新分配的对象可能直接进入正在并发标记的 Region 中,因此必须有 TAMS 这种机制来划清界限,默认存活,避免大量无效扫描。

    总结

    • SATB 以“逻辑快照”为核心,通过在删除引用侧记录旧对象,保障并发安全;
    • TAMS 用一条简单的地址指针,将标记期间的新对象默认标记为存活,极大降低扫描开销;
    • 两者一个解决“引用变更漏标”,一个解决“新对象识别”,共同支撑起 G1 高效、正确的并发标记。

    理解 SATB + TAMS,就抓住了 G1 并发标记的灵魂。希望本文能帮你彻底扫清这一核心知识点的障碍。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » G1 垃圾回收器 :SATB + TAMS核心机制深度解析
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!