跨代引用问题通过卡表解决:卡表是记忆集的具体实现,以512字节为单位标记老年代中可能含跨代引用的“脏卡”,配合写屏障实时更新,Minor GC仅扫描脏卡,大幅提升效率。

跨代引用问题的核心在于:Minor GC 只清理年轻代,但老年代对象可能正引用着某个年轻代对象。若不处理,这个被引用的对象会被误判为垃圾而回收,导致程序崩溃。JVM 不靠扫描整个老年代来解决,而是用“卡表”这种轻量级结构,把扫描范围压缩到极小的脏卡区域。
记忆集是逻辑设计,卡表是具体实现
记忆集(Remembered Set)是一个抽象概念——它只规定“要记录哪些非收集区指向收集区的引用”,不关心怎么存、存多细。就像“地图”是个功能需求,而纸质地图、电子导航是不同实现方式。HotSpot 选择用卡表来落地这个需求,因为它在精度、空间、维护成本之间取得了最佳平衡。
卡表本质是一个字节数组,每个字节对应老年代中一块 512 字节的内存区域(称为“卡页”)。只要该卡页内任意对象持有对年轻代的引用,对应字节就被标记为 1(即“变脏”)。地址计算非常快:card index = address >> 9(右移 9 位等价于除以 512)。
写屏障确保卡表实时准确
卡表不会自己更新。每当发生一次跨代赋值(比如老年代对象 A 的字段 a 指向了年轻代对象 B),JVM 就会在赋值操作前后插入一小段逻辑——这就是写屏障。
- 它会快速定位 B 所在的卡页,算出对应卡表索引
- 把该索引位置的字节设为 1(标记为 dirty)
- 这个过程开销极小,远低于 GC 时全扫老年代的成本
Minor GC 时只查“脏卡”,跳过干净区域
触发 Minor GC 时,GC 线程不再遍历整个老年代,而是:
- 扫描卡表中所有值为 1 的字节项
- 对每个“脏卡”,只解析其中存活的对象(而非全部对象)
- 把这些对象加入 GC Roots,再从它们出发做可达性分析
由于跨代引用极少,通常只有不到 1% 的卡页是脏的。这意味着 99% 的老年代内存被直接跳过,GC 停顿时间大幅下降。
卡精度是效率与精度的合理折中
记忆集理论上可按三种粒度记录:
- 字长精度:记录每个机器字是否含跨代指针(太细,空间爆炸)
- 对象精度:记录每个含跨代引用的老年代对象(仍需维护大量指针)
- 卡精度:只标记某 512 字节区域“可能有跨代引用”(够用且极省)
卡精度不保证每张卡里都有跨代引用,但它保证:所有真实存在的跨代引用,一定落在某张脏卡中。宁可多扫一点,也不漏扫一个——这是工程上最务实的选择。

















