记忆集和卡表是JVM跨代GC中用于高效处理跨代引用的协同机制:卡表以512字节卡页为单位粗粒度标记脏页,写屏障触发更新;记忆集则为每个分区维护精准引用来源,二者分层协作实现从粗筛到精查。

记忆集(Remembered Set)和卡表(Card Table)是 JVM 垃圾回收器(尤其是 G1、CMS 等跨代/分区收集器)用来高效处理跨代引用的核心机制。它们不直接参与对象标记或回收,而是**提前记录“谁可能引用了我”**,避免每次 GC 时全堆扫描。
卡表:用粗粒度标记缩小扫描范围
卡表是一种空间换时间的简化结构。它把 Java 堆划分为固定大小的“卡页”(通常 512 字节),每张卡页对应卡表中一个字节(或位)。当某卡页内的对象被其他代的对象引用(比如老年代对象引用了年轻代对象),JVM 就将该卡页在卡表中标记为“脏”(dirty)。
- 写屏障(Write Barrier)在发生跨代引用赋值时触发,自动标记目标卡页为脏——这是卡表生效的关键前提
- Young GC 时,只扫描卡表中标记为脏的卡页,从中找出所有可能包含老→新引用的字段,再精确检查这些字段
- 卡表本身极小(例如 8GB 堆仅需约 16MB 卡表),但能跳过大量无跨代引用的内存区域
记忆集:为每个分区维护精准引用来源
卡表适合快速筛选,但精度低(一个脏卡页里可能只有 1 个字段有跨代引用)。记忆集则更进一步:它为每个内存分区(如 G1 的 Region)单独维护一张“引用地图”,记录哪些其他分区中有对象指向本分区。
- 记忆集本质是哈希表或位图结构,键通常是源分区 ID,值是具体 card 或 offset 列表
- G1 在并发标记阶段构建记忆集;CMS 在初始标记后通过卡表扫描+写屏障增量更新
- Young GC 时,G1 直接查目标 Region 的记忆集,就知道只需扫描哪些 Region 的哪些卡页,无需遍历整个老年代
两者协同:从粗筛到精查
卡表和记忆集不是替代关系,而是分层协作:
- 写屏障先更新卡表(低成本),作为第一道过滤
- 后台线程或 GC 暂停期间,基于脏卡扫描结果,把真正存在跨代引用的条目聚合进对应 Region 的记忆集(更高成本,但更准)
- 下一次 GC 时,直接读记忆集,跳过卡表扫描环节,实现更快的根扫描
实际影响与调优提示
理解这两者有助于诊断 GC 性能问题:
- 如果 Young GC 时间长且 dirty card 数量激增,说明老年代对象频繁修改年轻代引用(如缓存类对象长期持有 List
- G1 中 -XX:G1RSetUpdatingPauseTimePercent 控制记忆集更新占用的 GC 时间比例,默认 10%,过高会延长 STW,过低导致记忆集滞后
- 卡表溢出(card table overflow)会导致退化为全堆扫描,可通过增大堆或减少大对象分配频率缓解
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











