RSet是G1收集器为解决跨Region引用而设计的反向索引表,每个Region维护“谁引用了我”,仅在老年代→年轻代或跨老年代赋值时通过写屏障触发更新,Young GC据此精准扫描相关脏卡,避免全老年代扫描。

G1 收集器通过 Remembered Set(简称 RSet)高效解决跨代引用问题,核心思路是:不扫描整个老年代来判断年轻代对象是否被引用,而是只检查可能持有跨代引用的内存区域——也就是每个 Region 的 RSet。
Remembered Set 是什么
RSet 是每个 Region 维护的一个“反向引用索引表”,记录了哪些其他 Region 中的对象引用了当前 Region 里的对象。例如,如果 Region A(属于年轻代)中的某个对象被 Region B(属于老年代)中的对象引用,那么 Region A 的 RSet 就会记录“Region B 中有字段指向我”。这样在回收 Region A 时,只需检查这些已知的引用源,无需遍历整个堆。
RSet 如何构建和更新
RSet 不是静态生成的,而是在程序运行中动态维护的:
- 写屏障(Write Barrier)拦截所有对引用类型字段的赋值操作(如 obj.field = otherObj)
- 当发现赋值目标(otherObj)位于不同 Region(尤其是跨代),且被赋值字段所在对象(obj)所在的 Region 有对应 RSet 时,就将源 Region(即 obj 所在 Region)标识为“可能引用了目标 Region”,并把该引用关系记录进目标 Region 的 RSet
- G1 使用一种叫 Per-Region Table 的结构(通常是卡表 Card Table 的扩展),配合并发标记和增量更新,避免写屏障开销过大
回收时如何利用 RSet 解决跨代问题
以 Young GC 为例:
立即学习“Java免费学习笔记(深入)”;
- 只收集年轻代 Region,但必须确保存活对象不被老年代“意外持有着”
- 扫描所有待回收 Region 的 RSet,获取其中记录的“外部引用源 Region”列表
- 对这些源 Region 中的引用字段做精确扫描(通常只扫描卡表中标记为 dirty 的 card),找出真正指向当前 Region 的引用
- 把这些引用对象加入 GC Roots,保证它们不会被误回收
这个过程让 G1 避开了传统分代收集器中“扫描整个老年代”的高成本操作,实现增量式、局部化的跨代引用处理。
RSet 的代价与优化
RSet 带来额外内存和 CPU 开销:
- 每个 Region 的 RSet 占用堆外内存(通常几 KB 到几十 KB),Region 越多总开销越大
- 写屏障带来轻微性能损耗,尤其在大量修改引用字段的场景
- G1 通过“RSet 更新延迟合并”“并发构建 RSet”“使用稀疏数组压缩存储”等方式缓解;JDK 10+ 还引入了“RSet 懒加载”和更精细的卡粒度控制
本质上,RSet 是用空间换时间、用局部精度换全局效率的关键设计,使 G1 在保持低延迟的同时支持大堆和分代假设。


















