RSet是G1中每个Region维护的反向索引结构,记录“谁引用了我”,按卡页粒度粗略登记跨Region引用,回收时仅扫描RSet所列脏卡,避免全堆扫描。

G1 垃圾回收器通过 Remembered Set(简称 RSet)避免全堆扫描,核心思路是:**只记录跨区域的引用关系,回收某个 Region 时,仅检查其 RSet 中登记的“可能指向它的外部引用”,而无需扫描整个堆。**
Remembered Set 是什么
RSet 是每个 Region 维护的一张“引用地图”,存储其他 Region 中哪些对象引用了本 Region 内的对象。它不保存具体引用地址,而是按卡(Card)粒度粗略记录——比如“Region A 的第 5 号卡里有指针指向本 Region”。这种设计在精度和开销间做了权衡。
RSet 如何规避全堆扫描
当 G1 对某个 Region 执行回收(如 Young GC 或 Mixed GC)时:
- 直接读取该 Region 的 RSet,获知哪些外部 Card 可能含指向它的引用
- 只将这些 Card 加入根集合(Root Set),作为并发标记或 STW 扫描的起点
- 完全跳过没有被 RSet 记录的 Region 和 Card,大幅缩小扫描范围
例如:Region X 要被回收,其 RSet 显示只有 Region Y 的 Card#12 和 Region Z 的 Card#7 可能引用它。G1 就只解析这两个 Card 内的引用,而不是遍历所有 Region 的所有对象。
立即学习“Java免费学习笔记(深入)”;
RSet 的维护机制保障低开销
RSet 不是静态快照,而由写屏障(Write Barrier)动态维护:
- 每次程序执行 obj.field = otherObj(且引用跨 Region)时,写屏障会触发,将目标字段所在的 Card 标记为“脏”,并异步加入 RSet 更新队列
- 更新由后台线程(如 Concurrent Refinement Threads)批量处理,避免拖慢应用线程
- 采用三级结构(Per-Region RSet + Global RemSet + Card Table)减少内存占用和冲突
调优提示:让 RSet 更高效
RSet 本身不消除跨 Region 引用,但可通过以下方式降低其维护成本与误报率:
- 避免频繁的跨 Region 大对象引用(如长期持有远端 Region 中集合的引用)
- 合理设置 -XX:G1HeapRegionSize:Region 太小 → RSet 条目爆炸;太大 → 卡精度下降、RSet 无效扫描增多
- 启用 -XX:+G1UseAdaptiveConcRefinement(默认开启),让系统自动调节并发 refinement 线程数和卡处理阈值
- 观察日志中 "Refine Record" 和 "RS scanned" 指标,确认 RSet 扫描量远小于总 Card 数


















