跨代引用记录机制的核心目标是确保minor gc时不漏掉被老年代对象引用的年轻代存活对象;通过卡表(512字节卡页映射字节数组)标记“脏”卡页,结合写屏障实时更新,并在gc时仅扫描脏卡页内的老年代对象,以高效解决跨代引用问题。

分代收集模型中,跨代引用记录机制的核心目标是:在只回收年轻代(Minor GC)时,不漏掉那些被老年代对象引用的年轻代存活对象。它不靠扫描整个老年代,而是用轻量、局部的方式精准定位“可能有跨代引用”的内存区域。
为什么必须处理跨代引用
Minor GC 默认只扫描年轻代和 GC Roots(如栈帧、静态变量等),但老年代对象完全不在扫描范围内。如果某个老年代对象 A 持有年轻代对象 B 的引用,而 B 又没被任何年轻代内对象引用,那么仅靠常规可达性分析,B 就会被误判为垃圾并回收——这会导致程序崩溃或数据异常。跨代引用虽少,但一旦遗漏,后果严重。
卡表(Card Table)是核心载体
HotSpot JVM 把老年代按 512 字节划分为一个个“卡页”(card),并维护一个字节数组作为卡表。每个数组元素对应一个卡页,值为 0 表示“干净”,值为 1 表示“脏”(dirty card)——即该卡页内至少有一个对象持有对年轻代的引用。
- 卡表大小固定,空间开销小(例如 1GB 老年代仅需约 2MB 卡表)
- 判断卡页索引只需地址右移 9 位(address >> 9),计算极快
- GC 时只遍历所有值为 1 的卡表项,再扫描对应卡页内的老年代对象,大幅缩小扫描范围
写屏障保障卡表实时更新
卡表本身不会自动知道何时变“脏”。JVM 在每次执行对象字段赋值(如 a.field = b)前,插入一小段“写屏障”逻辑:
- 检查 a 是否在老年代、b 是否在年轻代
- 若满足跨代条件,就将 a 所在卡页对应的卡表项设为 1
- 该过程由 JIT 编译器或解释器自动注入,对业务代码透明
写屏障让卡表始终反映最新引用状态,是跨代引用记录机制得以成立的运行时基础。
记忆集是更广义的设计概念
卡表其实是“记忆集”(Remembered Set)的一种具体实现,属于“卡精度”级别。记忆集本质是抽象的数据结构,用于记录“非收集区 → 收集区”的引用关系。不同收集器可采用不同精度:
- 字长精度:记录到机器字(如 64 位地址中的某字),粒度最细、开销最大
- 对象精度:记录到具体对象,平衡空间与维护成本
- 卡精度:记录到内存块(即卡表),当前主流选择,兼顾效率与简洁性
无论是 G1 还是 ZGC,都基于类似思想扩展记忆集(如 G1 的 Remembered Set 按 Region 维护),但卡表仍是新生代 GC 中最成熟、最广泛使用的方案。











