卡表是记忆集在HotSpot中的具体实现,通过512字节卡页与byte数组标记脏卡,结合写屏障实现高效跨代引用扫描;G1中升级为每个Region独立RSet,以哈希表+位图支持精准反向查找,调优需合理设置RegionSize、收拢数据分配并控制Mixed GC节奏。

卡表和记忆集不是两个并列技术,而是“实现与抽象”的关系:记忆集(Remembered Set)是解决跨代引用问题的通用设计思想,卡表(Card Table)是它在 HotSpot JVM 中最主流、最轻量的落地方式。优化的核心在于用空间换时间——不扫全堆,只查可能有引用的内存块。
卡表怎么把扫描范围缩到最小
HotSpot 将老年代(或其他非收集区)按 512 字节切分成“卡页”,每页对应卡表数组中的一个字节。只要某张卡页里有一个对象字段指向新生代(或目标收集区),这张卡就被标记为“脏”(值设为 1)。Minor GC 时,JVM 只需遍历卡表中所有值为 1 的索引,定位到对应的卡页,再扫描这些卡页内的对象即可——跳过 99% 以上干净区域。
- 卡页大小固定为 512 字节(地址右移 9 位),这是硬件寻址效率与精度的平衡点
- 卡表本身是 byte 数组,不用 bit 是为了规避位运算开销,提升写屏障触发时的吞吐
- 脏卡标记由写屏障(Write Barrier)自动完成:每次发生跨代赋值(如 oldObj.field = youngObj),JVM 就立刻把 oldObj 所在卡页标为脏
记忆集在 G1 里怎么升级得更聪明
G1 不再用全局卡表,而是为每个 Region 配一个独立的 Remembered Set(RSet)。RSet 本质是哈希表 + 卡页位图,只记录“哪些 Region 的哪些卡页里有对象引用我”。混合回收时,只需加载待回收 Region 的 RSet,反向查出源 Region,再精准扫描那几页卡——彻底避免无效遍历。
- RSet 膨胀只和“被多少 Region 引用”有关,跟自己 Region 里有多少对象无关
- 高频更新缓存(如 ConcurrentHashMap 的 value 跨 Region 分布)会大量触发 RSet 插入,成为瓶颈
- RegionSize 过小(如 512KB)会导致 Region 总数暴增,RSet 元数据吃掉大量堆外内存
调优的关键动作就三件事
卡表和记忆集本身无需手动开启,但它们的效率直接受 JVM 参数和代码习惯影响:
-
选对 RegionSize:电商商品快照约 4MB,就设
-XX:G1HeapRegionSize=4M;常见缓冲区 6–8MB,优先选 8M(必须是 2 的幂) - 收拢关联数据分配:把 key-value 对、消息头与 body、缓存 entry 与 value 尽量分配在同一 Region,减少跨 Region 引用频次
-
控制 Mixed GC 的回收节奏:加业务字段(如 cacheTTL)辅助识别冷热,或调
-XX:G1MixedGCCountTarget延长混合回收周期,给 RSet 合并留出时间
本质上,卡表是粗粒度兜底,记忆集是细粒度进化。用好它们,不是靠调参堆叠,而是让对象生命周期和内存布局更“守规矩”。










