首页 > 编程语言 >JVM如何实现跨代引用收集

JVM如何实现跨代引用收集

来源:互联网 2026-07-10 07:59:02

JVM利用记忆集、卡表和写屏障解决跨代引用收集难题。基于跨代引用极少的假设,通过卡表记录老年代可能存在跨代引用的卡页,写屏障实时维护卡表脏状态。MinorGC仅扫描脏卡页,避免扫描整个老年代,以空间换时间提升效率。

JVM 在处理跨代引用收集时,面临一个经典难题:Minor GC 明明只回收年轻代,但如果老年代中有对象引用着某个年轻代对象,这个年轻代对象就不能被回收。最简单的做法是每次都扫描整个老年代——但这显然不现实,尤其当老年代已经膨胀到几十 GB 时。于是 JVM 祭出了一套精巧的组合拳:记忆集(Remembered Set)卡表(Card Table)写屏障(Write Barrier)。说穿了,核心思路就一句话:用空间换时间,假设跨代引用极少,然后只记录可能出问题的地方

JVM如何实现跨代引用收集

长期稳定更新的攒劲资源: >>>点此立即查看<<<

具体来说,整个流程依赖一条关键假说——“跨代引用极少”。年轻代对象频繁创建又快速消亡,老年代对象往往活得长、很少突然指向年轻代。基于这个观察,JVM 的设计者决定:不记录每一条跨代引用,而是只标记“这个老年代内存块里可能存在跨代引用”。这样一来,Minor GC 时只需要扫描这些被标记的内存块,就能找出所有指向年轻代的老年代引用,进而保护那些不应该被回收的年轻代对象。

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

记忆集是一个抽象概念,它的作用说白了就是一张“清单”,登记了哪些非收集区域(通常是老年代)里可能含有指向当前收集区域(年轻代)的引用。Minor GC 时,GC Roots 除了线程栈、静态变量这些常规项,还会把记忆集中登记的这些对象也当作根——这样一来,老年代的其他区域就完全不用碰了。

  • 记忆集不关心每一条引用长什么样,它只关心精度。精度分好几级:字长精度(精确到内存地址)、对象精度(精确到具体对象)、卡片精度(精确到固定大小的内存块)。
  • HotSpot 默认使用卡片精度,因为它能在精度和实现开销之间找到一个很实用的平衡。
  • 记忆集本身不存引用内容,只存“这个地方可能有问题”的标记。

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

卡表是记忆集在 HotSpot 里的物理实现。简单说,它就是一个字节数组,每个字节对应老年代中的一块固定大小内存——这个固定大小的内存块叫做卡页(Card Page)

  • 在 HotSpot 中,卡页大小是512 字节。也就是说,老年代每 512 字节被划为一个“卡”。
  • 怎么找某个老年代地址对应的卡表字节呢?直接地址右移 9 位(因为 29 = 512)。比如地址 addr,卡表下标就是 addr >> 9,非常快。
  • 卡表值含义很简单:0 表示这个卡页里目前没有跨代引用;1(Dirty)表示这个卡页里至少有一个对象引用了年轻代对象,需要 GC 时重点排查。

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

卡表本身不会自己变脏,必须有人实时更新。谁来干这活?写屏障。每当老年代对象的某个字段被赋值——尤其是新赋的引用指向了一个年轻代对象——JVM 会在赋值指令之前或之后插入一小段代码。这段代码就是写屏障,它的唯一任务就是:检测这次赋值,然后把对应卡页标记为 Dirty。

  • 举个典型场景:执行 oldObj.field = youngObj 时,写屏障会立刻计算 oldObj 所在的卡页位置,并将对应的卡表项设为 1。
  • 写屏障的开销极小,但它的存在保证了卡表始终反映最新状态——任何跨代引用只要一建立,卡表马上就知道。
  • 不同的垃圾收集器(CMS、G1、ZGC 等)会用不同类型的写屏障,比如增量更新、SATB(Snapshot-At-The-Beginning)等,但目标一致:精准捕获引用变更。

Minor GC 时的实际扫描流程

一次年轻代 GC 到底怎么利用这些机制?流程并不复杂,但设计得非常巧妙。

  • 第一步:GC 线程遍历整个卡表,找出所有值为 1(Dirty)的项。
  • 第二步:对每一个 Dirty 卡页,扫描这个卡页内的所有对象,检查它们的每个字段是否引用了年轻代对象。
  • 第三步:如果找到了这样的引用,就把被引用的年轻代对象加入存活集合——这样它就不会被误回收了。
  • 第四步:扫描完成后,将已处理的 Dirty 卡页清零(或者根据收集器策略延迟清零),为下一次 GC 做好准备。

整个过程的关键点在于:不需要碰那些干净的卡页,因为它们内部要么没有老年代对象,要么老年代对象根本没有指向年轻代的引用。这样一来,GC 扫描的范围就从“整个老年代”缩小到了“少量 Dirty 卡页”,效率提升非常显著。而这套机制之所以能正常工作,根本前提还是那个假设——跨代引用确实很少。如果这个假设不成立,卡表里 Dirty 卡页暴增,写屏障的维护成本也会急剧攀升,那这套方案可能就扛不住了。好在实际情况中,这个假设基本成立,所以这套设计才成了 JVM 的标配。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。