Card Table 是 JVM 为解决 Minor GC 跨代引用漏标问题设计的轻量级结构,将老年代划分为512字节卡页,用字节数组标记“可能含老→年引用”的脏卡,仅扫描脏卡即可避免全老年代遍历。

Card Table 是 JVM 为解决 Minor GC 中跨代引用漏标问题而设计的轻量级辅助结构,它不记录具体引用关系,只标记“哪一块老年代内存可能持有对新生代的引用”——这块被标记的内存区域,就叫“脏卡”。
卡表怎么定义“一张卡”
JVM 把老年代按固定大小切分成连续的内存块,每块叫一个“卡(Card)”,大小硬编码为 512 字节,不可配置。所有卡对应一张字节数组(Card Table),每个字节代表一张卡:值为 0 表示“干净”,即该卡内对象当前未引用新生代;非零(通常是 0xFF)表示“脏”,意味着至少有一个老年代对象刚刚写入了指向年轻代对象的引用。
“脏卡”是怎么被标记出来的
标记动作由写屏障(Write Barrier)在赋值瞬间完成,不是 GC 时才补记:
- 仅当发生“老年代对象 → 新生代对象”的引用写入时触发,例如
oldObj.field = youngString - JVM 计算
oldObj地址对应的卡索引:(uintptr_t)oldObj >> 9(右移 9 位等价于除以 512) - 将 Card Table 中该索引位置设为非零值,标记为 dirty
- 若已为 dirty,可启用
-XX:+UseCondCardMark避免重复写入,降低开销
Minor GC 怎么靠脏卡提速
没有 Card Table,每次 Minor GC 都得扫描整个老年代(几 GB),STW 时间随堆增长线性上升;有了它,GC 只做两件事:
- 快速遍历 Card Table 数组,找出所有 dirty 的字节位置
- 仅扫描这些 dirty 卡对应的老年代内存块(共 512 字节/块),检查其中对象字段是否真引用了新生代
- 真正 dirty 的卡通常不足全部卡数的 0.1%,扫描量从 GB 级降到 KB 级
增量处理脏卡的实际方式
“增量”指空间分块、可并行,并非时间切片。JVM 将连续 dirty 卡打包成 stride,默认每块含 256 张卡(128KB 内存):
- 多线程 GC 时,每个 GC 线程领取一个 stride 独立扫描,无锁竞争
- 可通过
-XX:ParGCCardsPerStrideChunk调整 stride 大小(如设为 4K 或 8K),需结合 CPU 核心数与 GC 日志验证效果 - stride 过小 → 线程调度频繁;过大 → 任务不均,拖慢整体进度

















