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

Java 中老年代本身不会直接回收新生代对象,但老年代需要协助新生代 GC(如 Minor GC)快速识别跨代引用,避免扫描整个老年代。记忆集(Remembered Set,简称 RSet)和卡表(Card Table)正是为此设计的辅助数据结构,它们的作用是标记老年代中可能持有对新生代对象引用的位置,从而让 Minor GC 只检查这些“可疑区域”,大幅减少扫描开销。
卡表:粗粒度的内存标记机制
卡表将老年代(或整个 Java 堆)按固定大小(通常为 512 字节)划分为若干“卡页”(Card),每个卡页对应卡表中一个字节(byte)。当某块老年代内存中的对象字段被赋值为一个新生代对象时(即发生了跨代引用),JVM 会通过写屏障(Write Barrier)将对应卡页在卡表中标记为“脏”(例如设为 1)。
- 卡表本身很小——比如 1GB 老年代仅需约 2MB 卡表空间(1GB / 512B ≈ 2M 个字节)
- 写屏障在每次可能产生跨代引用的写操作(如
obj.field = youngObj)时触发,开销可控 - Minor GC 时,只遍历卡表中“脏”的卡页,解析其中的对象,检查其是否引用了新生代对象
记忆集:更精确的跨代引用记录(G1/Humongous 场景)
卡表是“粗筛”,而记忆集是“精筛”。在 G1 收集器中,每个 Region 都维护一个记忆集(RSet),记录其他 Region 中有哪些对象引用了本 Region 的对象。对新生代回收而言,RSet 主要用于:
- 当新生代 GC 需要扫描老年代中指向新生代的引用时,G1 不扫描整块老年代 Region,而是直接查该新生代对象所在 Region 对应的 RSet
- RSet 存储的是“谁引用了我”,所以它天然支持反向查找,比卡表更精准定位源引用位置
- RSet 一般采用哈希表或稀疏数组实现,内容为“源 Region 编号 + 卡页偏移”等摘要信息
两者如何协同工作
卡表和记忆集不是互斥关系,而是分层协作:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 卡表是 JVM 层通用、轻量的基础设施,CMS 和 G1 都依赖它做初始脏卡标记
- 记忆集是 G1 等先进收集器在卡表基础上构建的更高层索引结构,用于加速并发标记和混合回收
- 写屏障是关键枢纽:它既更新卡表,也异步更新 RSet(G1 中通过 Post-Write Barrier + 懒更新或并发 Refine 线程完成)
- Minor GC 时,先读卡表获得脏卡范围,再结合 RSet 快速提取有效跨代引用,避免漏标或全堆扫描
为什么不能跳过它们直接扫描老年代
假设老年代有 4GB,新生代只有 256MB。每次 Minor GC 若都扫描全部 4GB 老年代来找指向新生代的引用:
- 耗时不可接受(毫秒级变百毫秒级),严重拖慢吞吐和响应
- 违背分代假设——绝大多数老年代对象不引用新生代对象
- 无法满足低延迟目标(如 G1 的停顿时间预测模型依赖快速根扫描)
卡表与记忆集本质是用少量空间和写时开销,换取 GC 时的巨大时间收益,是分代收集高效运行的关键支撑。

















