g1的remembered set(rset)只记录跨region引用,不记录跨代引用;它为每个region维护反向映射表,标识哪些其他region的card包含指向本region的引用,通过写屏障与并发卡表处理实现高效增量更新。

G1 收集器的 Remembered Set(RSet)并不记录跨代引用,它记录的是跨 Region 的引用,且只关心“谁指向我”,而不是“我指向谁”。G1 已经取消了传统分代(如 Young/Old)的物理隔离,整个堆被划分为多个大小相等的 Region,每个 Region 可动态扮演 Eden、Survivor 或 Old 角色。因此,G1 中没有严格意义上的“跨代引用”概念,只有跨 Region 引用——而 RSet 正是用来高效追踪这类引用的。
RSet 的核心作用:标记“指向本 Region 的外部引用”
RSet 是一个“反向映射”结构,为每个 Region 维护一个表,记录哪些其他 Region 中的对象持有指向本 Region 内对象的引用。这样,在回收某个 Region 时,无需扫描整个堆,只需检查对应 RSet 中列出的少量 Region,就能确定哪些外部引用可能影响本 Region 的存活判断。
- RSet 不存储具体引用地址,而是按“卡片(Card)”粒度组织:堆被划分为 512B 的 Card,RSet 记录的是哪些 Region 中的哪些 Card 包含指向本 Region 的引用。
- 写屏障(Write Barrier)在每次发生跨 Region 的引用写入(如 obj.field = otherObj)时触发,将被修改引用所在的 Card 标记为“脏”,并加入更新队列(Dirty Card Queue),由后台线程异步处理,最终更新对应目标 Region 的 RSet。
- 例如:Region A 中的对象引用了 Region B 中的对象,写屏障会把 Region A 中包含该引用的 Card 加入队列;后续并发处理阶段,系统会扫描该 Card,发现对 Region B 的引用,于是将 Region A 的标识加入 Region B 的 RSet。
为什么没有“跨代 RSet”?
G1 虽保留分代逻辑(如 Young GC 只回收 Eden/Survivor Region),但 Region 的角色是动态的,且 RSet 的设计与分代无关:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- Young GC 时,仅需扫描 RSet 中指向 Eden/Survivor Region 的外部引用(可能来自老年代 Region 或其他年轻代 Region),这些引用都已由 RSet 按 Region 归属组织好。
- Mixed GC 回收部分 Old Region 时,同样依赖各待回收 Region 自己的 RSet,来快速定位所有可能指向它们的外部引用源 Region。
- 分代信息(如某 Region 当前是 Young 还是 Old)不参与 RSet 构建逻辑;RSet 只认 Region ID 和 Card 偏移。
RSet 的实际维护方式
RSet 的底层实现是一个哈希表或稀疏数组,Key 是源 Region ID,Value 是该 Region 内“脏 Card”的位图或索引集合:
- 每个 Region 对应一个 RSet 实例,内存开销可控(通常占堆的 1%~5%,取决于跨 Region 引用密度)。
- RSet 更新是延迟且并发的:写屏障只做轻量标记,RSet 的实际构建由多个 GC 后台线程(如 Refine Threads)并行完成,避免阻塞 Mutator。
- 当 Region 被回收或重新分配角色时,其 RSet 会被清空或复用,不绑定生命周期。
RSet 是 G1 实现增量式、可预测停顿的关键基础设施,它让收集器摆脱了全堆扫描,真正做到了“按需知道谁引用了我”。理解它不服务于分代,而服务于 Region 间的引用可见性,是掌握 G1 工作机制的重要一步。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










