发布于2026-07-10 阅读(0)
扫一扫,手机访问
如果要理解G1垃圾回收器的RSet,一个核心前提是必须搞懂跨代引用的捕获机制。跨代引用的变更——比如老年代对象突然改了对年轻代字段的引用——这类操作随机、高频,而且完全不可预测。如果等到Young GC时再去扫描整个老年代、翻找这些引用关系,那停顿时间就会彻底失控,这显然不是我们想看到的结果。RSet的本质,说白了就是“延迟归集 + 精准定位”:它并不实时维护完整的引用关系,而是靠写屏障捕获每一次跨代写操作,在对应的目标Region的RSet中记下源Card的地址。
很多开发者容易陷入一个误解:以为RSet是GC时才开始构建的。其实不然,每次发生obj.field = otherObj这类跨代赋值(前提是otherObj在不同的Region),RSet就会立即被更新。假如通过-XX:-UseCondCardMark之类的方式禁用写屏障,RSet就会漏记引用,Young GC阶段可能漏扫存活对象,进而导致对象被提前回收,甚至引发JVM崩溃。这里有几个关键细节值得标记:G1使用后置写屏障(Post-barrier),即在赋值完成后才执行RSet插入逻辑;插入粒度并非对象地址,而是“源对象所在Card的起始地址”,每个Card默认512字节;底层是并发哈希表,多线程写入时有锁或无锁分段机制,不过高竞争场景下,它还是会成为瓶颈。

因为跨代引用的变更(比如老年代对象修改了对年轻代字段的引用)是随机、高频且不可预测的,如果等到 Young GC 时再扫描整个老年代找引用,停顿时间就失控了。RSet 的本质是「延迟归集 + 精准定位」:不实时维护完整引用关系,而是靠写屏障捕获每一次跨代写操作,在对应目标 Region 的 RSet 中记录下源 Card 的地址。
常见错误现象是误以为 RSet 是 GC 时才构建的——实际上它在每次 obj.field = otherObj 发生跨代赋值时就被更新(前提是 otherObj 在不同 Region)。若禁用写屏障(如通过 -XX:-UseCondCardMark),RSet 就会漏记,Young GC 可能漏扫存活对象,导致提前回收或崩溃。
Young GC 的根扫描阶段必须检查两类 Roots:一是线程栈/静态变量等原生 Roots;二是所有被老年代引用的年轻代对象——这部分就靠遍历当前 Eden/Survivor Region 对应的 RSet 来完成。RSet 越大、条目越多,扫描耗时越长,直接抬高 STW。
典型性能陷阱是巨型对象(Humongous Object)频繁被老年代引用:一个 Humongous Region 可能横跨多个 Card,而每个被引用的 Card 都会在多个目标 Region 的 RSet 中留下条目,造成 RSet 膨胀。实测中,RSet 占用可达堆内存的 10%~20%,且其扫描开销与条目数基本呈线性关系。
G1RSetScannedCards(已扫描 Card 数)、G1RSetUpdates(写屏障触发次数)可通过 -XX:+PrintGCDetails 查看-XX:G1RSetRegionEntries=N 控制单个 Region 的 RSet 最大条目数,超限后退化为全 Card 扫描(慎调)CMS 只需处理「老→年轻」引用,所以卡表(Card Table)够用;但 G1 的 Mixed GC 要回收部分老年代 Region,就必须知道「哪些其他老年代 Region 持有对本 Region 的引用」——这就要求 RSet 不仅记录老→年轻,还要记录老→老、甚至年轻→老(用于反向追踪晋升对象是否还被引用)。
这意味着每个 Region 的 RSet 实际上是一张小范围的「引用关系索引表」。当 G1 决定回收某个 Old Region A 时,它不会去扫描整个堆,而是只查 A 的 RSet,拿到所有可能引用 A 的源 Region 列表,再精准扫描这些源 Region 中对应的 Card。这是 G1 实现增量式老年代回收的底层前提。
-XX:G1ConcRefinementThreads 参数:控制并发 Refinement 线程数,负责把写屏障缓冲区中的 dirty card 批量刷入 RSet;设太小会导致 RSet 更新滞后,Young GC 可能漏扫光看 GC 日志不够,得结合 RSet 相关计数器判断是否成为瓶颈。JDK 自带的 jstat 和 GC 日志中的特定字段最实用。
开启详细 RSet 统计:启动时加 -XX:+PrintAdaptiveSizePolicy -XX:+PrintGCDetails -XX:+UseStringDeduplication,重点关注每轮 Young GC 后的 RSet 行:
[GC Worker Start (ms): 12345.678][Ext Root Scanning (ms): 2.123][RSet Scanning (ms): 18.456] <-- 这里是关键[Object Copy (ms): 32.789]
RSet Scanning (ms) 超过总 STW 30%,说明 RSet 已成主要瓶颈jstat -gc 中的 EC(Eden Capacity)、EU(Eden Used)变化不大但 YGC 频次上升,常伴随 RSet 条目暴增jcmd VM.native_memory summary scale=MB 查看 Internal 区域内存占用,若持续增长,大概率是 RSet 元数据膨胀RSet 本身不是黑盒,它的设计取舍非常明确:用运行期写屏障的小代价,换取 GC 阶段的可控性。真正难的是在业务代码里识别出那些无意中制造跨代引用热点的地方——比如一个长期存活的 Map 缓存了刚 new 出来的 DTO,这种引用关系会一直拖着 DTO 晋升,同时不断往 RSet 塞新条目。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8