JVM垃圾回收机制深度解析(G1篇)(垃圾回收过程及专业名词详解)(补充)

本文将补充讲解JVM垃圾回收机制深度解析(G1篇)(垃圾回收过程及专业名词详解)-CSDN博客这篇文章中没讲到的一些细节:

首先:

那么问题时:图中的并发标记阶段,存活的对象在TAMS指针前还是后?

在G1垃圾回收器中,并发标记阶段 存活的对象位于 TAMS指针之后

关键机制解析

  1. TAMS的定义

    TAMS(Top at Mark Start)是G1回收器中的一个指针,它标记了并发标记开始时堆内存的"顶部"。

    • TAMS之前(下方): 存放的是并发标记开始时就已经存在的对象。G1会扫描这些区域来标记存活对象。
    • TAMS之后(上方): 存放的是在并发标记过程中,应用程序线程新分配的对象。
  2. 隐式标记

    在并发标记阶段,应用程序线程依然在运行并分配新对象。G1不需要专门去"标记"这些新对象为存活,而是采用了一种更高效的方式:

    • 新分配的对象直接被分配在TAMS指针之上的区域。
    • G1回收器默认认为 TAMS之上的所有对象都是存活的(即"隐式存活")。

总结

  • TAMS之前: 需要通过遍历对象图来显式标记哪些是存活的,哪些是垃圾。
  • TAMS之后: 所有对象默认视为存活(因为它们是在标记开始后新创建的)。

问题又来了:最终标记阶段什么作用?

G1垃圾回收器中的 最终标记阶段(Final Marking) 是整个并发标记周期中最关键的 Stop-The-World(STW) 阶段之一,其核心作用是:

修正并发标记期间因用户线程并发修改对象引用而导致的"漏标"问题,确保所有真正存活的对象都被准确标记。


一、为什么需要最终标记?

并发标记阶段(Concurrent Marking) ,GC线程与应用线程并行运行。这虽然减少了停顿时间,但也带来了风险:
应用线程可能在标记过程中修改对象引用关系,例如:

  • 删除一个老对象对某对象的引用(使其本应变为垃圾);
  • 新增一个老对象对某白色对象的引用(使其本应存活却被漏标)。

如果不处理这些变化,就可能导致:

  • 错误回收存活对象(严重!会导致程序崩溃);
  • 或者产生大量"浮动垃圾"(影响内存效率)。

因此,必须在一个 一致的内存快照下 进行一次全局修正 ------ 这就是 最终标记阶段 的使命。


二、最终标记阶段的核心工作

1. 处理 SATB(Snapshot-At-The-Beginning)日志
  • 在并发标记开始时,G1通过写屏障记录了"初始快照"。
  • 并发期间,任何被 断开引用 的对象(即原引用的目标)会被写屏障捕获,并加入 SATB 缓冲队列
  • 在最终标记阶段,GC会 遍历所有 SATB 队列,将这些"可能被漏标的对象"重新标记为存活(通常标记为灰色,加入标记栈继续传播)。

✅ 目的:防止因引用删除导致的"误回收"。

2. 扫描脏卡(Dirty Card Scanning)并更新记忆集(RSet)
  • 并发期间,跨 Region 引用的变更会通过写屏障标记对应的 卡表(Card Table)为"脏"
  • 最终标记阶段会 扫描所有脏卡 ,从中提取出 新增的跨 Region 引用
  • 利用这些引用,更新目标 Region 的 Remembered Set(RSet) ,并 重新扫描这些引用指向的对象,确保它们被正确标记。

✅ 目的:防止因新增引用导致的"漏标"。

3. 完成三色标记的收尾
  • 将所有待处理的灰色对象继续标记,直到标记栈为空。
  • 确保整个堆的标记状态达到 一致且完整
4. 清理与元数据更新
  • 清理无效的 RSet 条目;
  • 更新每个 Region 的 存活对象统计信息(用于后续 Mixed GC 的 Region 选择);
  • 为接下来的 筛选回收(Evacuation)阶段 准备数据。

三、总结:最终标记阶段的作用

表格

作用 说明
保证标记准确性 修正并发期间引用变更造成的漏标或误标
处理 SATB 日志 回收"快照中存在但后来被断开引用"的对象,避免误删
同步 RSet 状态 确保跨 Region 引用被完整记录,支持精确回收
生成回收决策依据 统计各 Region 存活率,为 Mixed GC 选择高收益 Region

💡 关键点 :最终标记是 短暂停顿但高价值 的 STW 阶段,它牺牲少量暂停时间,换取了标记结果的 强一致性安全性,是 G1 实现低延迟、高可靠 GC 的基石。

简言之:最终标记 = 并发标记的"纠错+收尾"阶段,确保"该活的都活,该死的才死"。

最后一个问题:

如果在并发标记阶段,删除了一个老对象对某对象的引用,最终标记阶段会怎么处理?如果在并发标记阶段,是新增一个老对象对某白色对象的引用,最终标记阶段会怎么处理?

回答如下:

背景知识:G1 使用 SATB(Snapshot-At-The-Beginning)机制

G1 的并发标记基于 SATB(快照在开始时) 算法。其核心思想是:

在并发标记开始时拍一个"逻辑快照",所有在这个快照中存活的对象,即使后来变成不可达,也不能被回收。

为了实现这一点,G1 通过 写屏障(Write Barrier) 在对象引用发生变更时记录相关信息。


场景一:删除一个老对象对某对象的引用

示例:

java 复制代码
// 并发标记开始前:A(黑色/已标记) → X(白色)
// 并发标记期间,应用线程执行:
A.ref = null;  // 删除 A 对 X 的引用

问题:

X 是否会被错误回收?

→ 如果 X 没有其他引用,它在快照中是可达的(因为 A 引用了它),但后来被断开。

→ 根据 SATB 原则 ,X 仍然必须被视为存活,因为它在快照中是可达的!

最终标记阶段如何处理?

  • A.ref = null 执行时,写屏障会捕获"被覆盖的旧值" ,即 X
  • 这个 X 会被加入 SATB 缓冲队列(satb_mark_queue)
  • 最终标记阶段(STW) ,GC 线程会:
    1. 遍历所有线程的 SATB 队列;
    2. X 重新标记为灰色(如果尚未标记);
    3. 继续从 X 出发遍历其子对象,确保整个子图都被标记。

结果 :X 不会被回收,符合"快照语义"------保证了 不误删(no false negatives)。
注意:即使 X 后来真的变成垃圾,也要等到 下一轮 GC 周期 才能回收。这是 SATB 的代价,但换来的是安全性。


场景二:新增一个老对象对某白色对象的引用

示例:

java 复制代码
// 并发标记期间,应用线程执行:
B.ref = Y;  // B 是已标记的黑色老对象,Y 是白色对象(尚未被标记)

问题:

Y 是否会被漏标?

→ B 已经是黑色(标记完成),不会再被扫描;

→ Y 是白色,且没有其他灰色对象引用它;

→ 如果不处理,Y 会被当作垃圾回收 ------ 严重错误!

最终标记阶段如何处理?

这里的关键在于:G1 的写屏障不仅记录"被删除的引用",也记录"新增的跨代/跨 Region 引用"

具体流程:

  1. B.ref = Y 执行时:
    • 如果 BY 属于 不同 Region (尤其是老年代 → 老年代 或 老年代 → 新生代),写屏障会将 B 所在的 Card 标记为"脏"
  2. 最终标记阶段
    • GC 会扫描所有 脏卡(Dirty Cards)
    • 通过 Remembered Set(RSet) 重建 B 所在 Region 的外部引用;
    • 发现 B → Y 这条新引用;
    • Y 标记为灰色(如果仍是白色),并加入标记栈继续传播。

结果 :Y 被正确标记为存活,避免了 漏标(missed live object)。
补充:如果 BY同一个 Region,且该 Region 正在被完整扫描,则可能不需要额外处理。但 G1 通常仍会通过卡表机制保守处理。


总结对比

场景 操作 风险 G1 处理机制 最终标记阶段动作
删除引用 A.ref = null 快照中存活的对象被提前回收 SATB 写屏障 捕获旧值 X 从 SATB 队列取出 X,重新标记
新增引用 B.ref = Y(B 黑,Y 白) Y 被漏标回收 卡表 + RSet 记录新跨 Region 引用 扫描脏卡,发现 B→Y,标记 Y

核心结论

G1 通过 双重保障机制 解决并发标记中的引用变更问题:

  1. SATB(快照机制) → 解决 "删除引用" 导致的 误回收
  2. 卡表 + Remembered Set → 解决 "新增引用" 导致的 漏标

最终标记阶段 正是将这两种机制收集到的信息 统一处理、完成标记闭环 的关键 STW 阶段。

因此,无论是引用删除还是新增,G1 都能在最终标记阶段确保:所有在逻辑快照中或之后应存活的对象,都不会被错误回收。

相关推荐
無限進步D3 小时前
Java 运行原理
java·开发语言·入门
難釋懷3 小时前
安装Canal
java
是苏浙3 小时前
JDK17新增特性
java·开发语言
不光头强3 小时前
spring cloud知识总结
后端·spring·spring cloud
SPC的存折5 小时前
1、Redis数据库基础
linux·运维·服务器·数据库·redis·缓存
爱学习的小囧6 小时前
VMware ESXi 6.7U3v 新版特性、驱动集成教程和资源包、部署教程及高频问答详情
运维·服务器·虚拟化·esxi6.7·esxi蟹卡驱动
小疙瘩6 小时前
只是记录自己发布若依分离系统到linux过程中遇到的问题
linux·运维·服务器
GetcharZp6 小时前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
dldw7776 小时前
IE无法正常登录windows2000server的FTP服务器
运维·服务器·网络
阿里加多6 小时前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang