卡表变脏仅由写屏障触发,专用于标记老年代指向年轻代的引用;其通过地址右移9位计算索引并置对应字节为非零值,不记录引用细节,仅指示需扫描的512字节区域;绕过写屏障将导致漏标和错误回收。

卡表变脏的标记更新完全依赖写屏障(Write Barrier)机制,不是由 GC 主动扫描或定时刷新产生的。
写屏障触发标记动作
当老年代中某个对象的字段被赋值为指向年轻代对象时(例如 obj.field = youngObj),JVM 会在该赋值操作前后插入写屏障逻辑:
- 计算目标对象地址在卡表中的索引:(uintptr_t)obj >> 9(等价于地址除以 512)
- 将卡表中对应字节设为非零值(如 0xFF),表示该卡页“变脏”
- 这个过程极快,通常由 CPU 原子指令支持,对业务线程影响极小
只对“老→年轻”写入生效
卡表的脏标记有明确方向性,仅响应从老年代向年轻代的引用写入:
- 老年代对象字段指向 Eden 或 Survivor 区对象 → 触发标记
- 年轻代对象指向老年代、老年代内部引用、或年轻代内部引用 → 不触发
卡表本身不记录引用细节
卡表是一个纯标记结构,每个字节只表达“该 512 字节内存区域可能含跨代引用”,不保存任何地址、字段名或引用关系:
- 它不是引用登记表,也不是 Remember Set(RSet)
- 它的作用是缩小 Minor GC 扫描范围,把“查哪里”变成“查哪些卡页”
- 真实跨代引用的识别和登记,由后续的 RSet 构建线程完成
绕过写屏障会导致漏标
如果通过 JNI、Unsafe 或反射直接修改内存,跳过 JVM 的写屏障逻辑:
- 即使发生了老→年轻引用,卡表也不会被置为 dirty
- Minor GC 就不会扫描对应卡页,导致年轻代对象被错误回收
- 典型表现是 NullPointerException 或数据不一致,且难以复现

















