前言
跨代引用
场景:
- 年轻代的对象持有着老年代对象的引用、老年代的对象持有着年轻代对象的引用
特点:
- 互相引用的两个对象几乎总是同生共死:
- 如果某个新生代对象存在跨代引用,由于老年代对象难以消亡,该引用会使得新生对象在minor gc时得以存活,该对象经历多次minor gc后晋升到老年代,此时跨代引用自然也随着该对象的晋升而消失了。
- 因此,存在跨代引用的对象较少。
跨代引用带来的问题:
- 年轻代对象引用老年代对象:minor gc时需要扫描老年代。
- 老年代对象引用年轻代对象:major gc时需要扫描年轻代。
解决方案:
- 通过降低扫描对象的范围或数量来降低gc的耗时:
- 年轻代对象引用老年代对象:借助 卡表/记忆集合 来减小minor gc时扫描老年代的范围,进而降低minor gc的时间。
- 老年代对象引用年轻代对象:
- CMS收集器:借助 提前触发minor gc 来减少年轻代中对象的数量,进而降低major gc的时间。
- G1收集器:借助 记忆集合 来减少老年代gc时扫描年轻代的范围,进而降低minor gc的时间。
卡表
数据结构:
- 卡表:jvm将老年代划分为若干个大小为512字节的区域(card),并使用一个 字节(byte)数组 来标记老年代中这些区域(card)中的对象是否持有新生代对象的引用。jvm将这个 字节数组 称为卡表(card table)。
- 卡表中的元素:表示老年代中某块区域(card)中的对象是否持有新生代对象的引用。
- 卡表属于points-out(我引用了谁的对象)的结构。
说明:之所以使用byte数组而不是bit数组主要是速度上的考量,现代计算机硬件都是最小按字节寻址的,没有直接存储一个bit指令,所以要用bit的话就不得不多消耗几条shift+mask指令。
原理:
- 当老年代中的某个对象持有了新生代对象的引用时,jvm将卡表中表示该对象所在区域(card)的元素设为1,表示该对象所在区域(card)是一个 dirty card。(注意:新生代对象引用老年代对象时,老年代对象所在的区域(card)不会被标记为dirty card)。
- 年轻代gc时只扫描dirty card中的对象,而无需扫描整个老年代中的对象,从而减少年轻代gc的停顿时间。
- 当完成所有脏卡的扫描之后,jvm便会将所有脏卡的标识位清零。
card标记为dirty card的原理
写屏障:
- 写屏障是一小段将card标记为dirty card的代码:检查对象的引用变更时是否出现了跨代引用(g1是跨region引用),如果出现,这将对应的card标记为dirty card。
Hotspot VM的字节码解释器和JIT编译器使用写屏障维护卡表:
- 解释器每次执行更新引用的字节码时,都会执行一段写屏障。
- JIT编译器在生成更新引用的代码后,也会生成一段写屏障。
- 虽然写屏障使得应用线程增加了一些性能开销,但是minor gc的效率提高了很多,进而提高了系统的吞吐量。
思考:
minor gc时只扫描dirty card中的对象,并行收集模式下gc线程有多个,那么针对多个dirty card,这些gc线程是如何分工的呢?
步块
数据结构:
- jvm将一定数量(默认是256)的card组成一个更大区域,这个区域称为步块(stride chunk)。
作用:
- 多个gc线程并行收集时,每个线程每次负责扫描一个步块,其中包括:扫描该步块对应的 部分卡表 以及 该步块内dirty card中的对象。
参数:
-XX:+UnlockDiagnosticVMOptions
- 解锁诊断虚拟机参数,否则 XX:ParGCCardsPerStrideChunk 不生效
-XX:ParGCCardsPerStrideChunk
- 设置一个gc线程每次扫描的card数量,默认是256。
- 不能设置的太小:太小会造成老年代stride chunk数量太多,导致gc线程在stride chunk之间切换的开销增加,进而导致gc暂停时间增加。
- 经验值:堆大小为4G时,该值设为2k,具体取值以最终调优结果为准。参考:
记忆集合
数据结构
- 每个region都维护着一个记忆集合(Remembered Set / Rset),收集器在标记跨代引用的对象时只需扫描(CSet中region维护的)RSet即可。
- RSet的整体结构是一个哈希表,底层是在卡表的基础上实现的。
- key:key记录了引用本region中对象的对象所在region的位置。
- value:是一个集合,其元素是:其它region(由key确定是哪个region)中的对象引用本region中对象的引用及引用所在的卡表位置。
- Rset属于points-into结构(谁引用了我的对象)
RSet、Card和Region的关系
- 每个region被分成了多个card。
- 不同region中的card会相互引用。
- 图示:
- Region1中的Card中的对象引用了Region2中的Card中的对象,蓝色实线表示的就是points-out的关系,而在Region2的RSet中,记录了Region1的Card,即红色虚线表示的关系,这就是points-into。
- 维系RSet中的引用关系靠post-write barrier和Concurrent refinement threads来维护 。
参考: