记忆集(rset)是jvm解决跨代引用的核心机制,它通过记录老年代中存在跨代引用的内存块(卡页),使minor gc仅扫描“脏卡”而非全量老年代,从而在保障gc正确性的同时显著降低停顿时间;其具体实现为卡表,依赖写屏障实时维护准确性。

记忆集(Remembered Set,简称 RSet)是 JVM 解决跨代引用问题的核心机制,它不靠暴力扫描整个老年代,而是用一种“按需标记 + 精准扫描”的方式,在保证 GC 正确性的前提下大幅降低开销。
为什么跨代引用会出问题
Minor GC 只清理新生代,但老年代对象可能正引用着某个新生代对象。如果忽略这种引用,GC 会误判该新生代对象为垃圾并回收,导致程序崩溃。而老年代通常远大于新生代,每次 Minor GC 都去全量扫描老年代,停顿时间会从几毫秒飙升到几百毫秒——这完全违背分代收集的设计初衷。
记忆集怎么工作
记忆集是一个逻辑结构,作用是:记录“哪些非收集区域(如老年代)的内存块中,存在指向当前收集区域(如新生代)的引用”。它不记录每一条引用细节,只回答一个问题:“这块老年代内存里有没有跨代指针?”
- 它存放在新生代(或 G1 中每个 Region)内部,与被收集区域绑定
- GC 开始前,把记忆集中标记为“脏”的内存块对应的老年代对象,加入 GC Roots 一起扫描
- 这样既覆盖了所有真实跨代引用,又跳过了大量无跨代引用的老年代区域
卡表是记忆集最常用的实现方式
HotSpot JVM 实际用的是卡表(Card Table),它是记忆集的一种具体落地形式。JVM 把老年代划分为若干个固定大小的“卡页”(通常 512 字节),每张卡页对应卡表里一个字节(或位)。
- 当老年代对象修改字段、指向新生代对象时,写屏障(Write Barrier)会立即标记对应卡页为“脏”
- Minor GC 时,只遍历卡表中“脏”的那些卡页,定位到对应的老年代对象,再将其加入根集合
- 这种“粗粒度标记 + 细粒度扫描”的组合,兼顾了空间效率和维护成本
写屏障保障记忆集实时准确
记忆集本身不会自动更新,必须依赖写屏障在每次对象引用赋值时介入:
- 比如执行
oldObj.field = youngObj,写屏障检测到这是跨代写入,就触发卡表对应位置置为 dirty - 没有写屏障,记忆集就会过期,GC 就可能漏标——这是严重错误
- 不同 GC 器使用的写屏障类型不同(如 G1 用 SATB,ZGC 用读屏障+染色指针),但目的都是让记忆集始终可信
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











