一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

JVM 如何实现跨代引用收集

时间:2026-07-10 10:21:46 编辑:袖梨 来源:一聚教程网

JVM通过记忆集、卡表和写屏障协同实现跨代引用收集,避免Minor GC时全量扫描老年代。记忆集逻辑记录跨代引用位置,卡表以512字节为单位标记脏卡页,写屏障实时更新卡表状态,GC仅扫描Dirty卡页中的跨代引用。

JVM 实现跨代引用收集,核心是避免 Minor GC 时扫描整个老年代,同时确保被老年代对象引用的年轻代对象不被误回收。它依赖“跨代引用极少”这一关键假说,用空间换时间,通过 记忆集(Remembered Set) 和其物理实现 卡表(Card Table) 配合 写屏障(Write Barrier) 协同工作。

记忆集:逻辑上的跨代引用索引

记忆集是抽象概念,作用是记录“哪些非收集区域(如老年代)中存在指向当前收集区域(如年轻代)的引用”。Minor GC 时,GC Roots 不仅包含线程栈、静态变量等,还会加入记忆集中登记的跨代引用对象——这样就不必遍历全老年代。

  • 它不记录每一条具体引用,而是按精度分级:字长精度(精确到地址)、对象精度(精确到对象)、卡片精度(精确到一段内存)
  • HotSpot 默认采用卡片精度,平衡开销与实用性
  • 记忆集本身不直接存储引用,而是标记“可能有引用”的内存块位置

卡表:记忆集在 HotSpot 中的具体落地

卡表是记忆集最主流的物理实现,本质是一个字节数组,每个元素对应老年代中一块固定大小的内存区域(称为“卡页”,Card Page)。

  • HotSpot 中卡页大小为512 字节,即老年代每 512 字节划为一个卡
  • 卡表数组索引由地址计算:比如某老年代地址 addr,对应卡表下标为 addr >> 9(右移 9 位等于除以 512)
  • 值为 0 表示该卡页未发生跨代引用;值为 1(Dirty)表示该卡页内至少有一个对象引用了年轻代

写屏障:实时维护卡表脏状态的关键机制

每当老年代对象的字段被赋值(尤其是新引用指向年轻代对象),JVM 会在赋值指令前后插入一小段代码——这就是写屏障。它的职责是检测并标记相关卡页为 Dirty。

  • 例如执行 oldObj.field = youngObj 时,写屏障会计算 oldObj 所在卡页位置,并将对应卡表项设为 1
  • 写屏障开销很小,但能保证卡表始终反映最新跨代引用状态
  • 不同垃圾收集器(如 CMS、G1、ZGC)使用不同类型的写屏障(如增量更新、SATB),但目的相同:精准捕获引用变更

Minor GC 时的实际扫描流程

一次年轻代 GC 并不从头扫描所有老年代对象,而是聚焦于卡表中标记为 Dirty 的卡页。

  • GC 线程遍历卡表,找出所有值为 1 的项
  • 对每个 Dirty 卡页,扫描其中所有对象,检查其字段是否引用年轻代对象
  • 若发现引用,则将被引用的年轻代对象加入存活集合,避免被回收
  • 扫描完成后,将已处理的 Dirty 卡页清零(或延迟清零,取决于收集器策略)

热门栏目