下图的 各个区域的 C++ 实现是 一般情况下的
年轻代的计算是会动态调整的,假如年龄阈值是 3 的对象占用了 Survivor (为什么不是 eden+survivor?) 的 50%以上
并且年轻代对象的年龄是 1 ~ 5
并且一开始设置的年龄阈值是 15, 那么年龄阈值将被调整到 3
下次 young gc 会把年龄是 3~5 的对象 promote
1.GenCollectedHeap::process_roots
假如 3 和 9 的年龄未到阈值,被复制到了 to 区
6 和 8 的年龄到了阈值,被 promote 到了 老年代
被promote的对象会被 一条链表连接起来,也就是下图的红线
实际上,只是用对象的对象头去把对象连接起来(这里说的对象都是 C++ 层面对象的具体实现,也就是 oopDesc 的对象 在内存中占用的一段内存块)
spool Block 是一块块独立于堆区之外的内存,是JVM用于管理的内存,当对象的对象头要被用来做其他用途的时候(比如下图就是用来连接其他对象)
如果对象头里有重要的信息,比如年龄,锁信息等,就需要保存对象头,具体是保存到Spool Block中,而且是紧挨着存放,一个block用完了用下一块,老套路了。
使用 对象头做别的事情也是老套路了,比如可以把对象头设置成特定值,辨别是否被 mark,这一点在老年代的压缩回收中用到
接着是要处理脏卡表,也就是用户修改了引用的地方
假设对象 15 所在的 卡表是脏的,也就是 对象 15 本来有引用是指向 对象1,但是引用之前修改了,指向了 对象4,那么它所在内存区域的卡表就会被设置为脏的
这时候就需要把 对象4 按之前的方式,看看是否年龄到了阈值,是的话就复制到 to,否则promote到老年代
假设上面步骤得到的 存活对象的集合为 T0 (to区对象,promote到老年代的对象)
2.FastEvacuateFollowersClosure::do_void
以刚刚得到的存活对象集合 T0(to区对象,promote到老年代的对象)为起点,广度优先遍历所有可达的对象,也采取同样的方法处理他们(到达阈值就拷贝到老年代,否则到to区),其实处理之后他们就变成了新的 T0, 之后会一直以他们为起点,重复之前的动作
直到遍历到 T0 对象没有引用对象为止。
遍历停止的条件是 no_allocs_since_saved_mark, 老年代和年轻代有自己不同的实现
年轻代的判断依据是 to 区没有对象再添加进来,因为有对象添加进to 区的话,说明还有对象 因年龄不够而被加入 to 区, 这样的话又要遍历这个对象的引用
老年代的判断依据是 promotionInfo 链表为空,也就是上面的红色线连起来的链表 没有节点,也就是没有新的被 promote 到 老年代的对象
年期代是从 零开始的,所以脏卡表也是从 零开始的,垃圾回收开始之前,脏卡表已经记录了 老年代指向年期代的所有应用
在此基础上,遍历脏卡表的时候,会把脏卡表项置为 clean, 然后再去遍历这块脏卡表对应内存区域对象的引用,只要这些引用都不为 null,就会把这些引用对应的对象复制到 to 或 晋升到 老年代, 同时把 之前置为 clean 的卡表 置为 younggen_card
表明这个卡表项对应的卡表块中的老年代,还有引用年轻代对象(to,如果是复制到老年代的话...我就不知道为什么还要变成 younggen_card 了,younggen_card 这个值其实也是脏的意思吧)