在枚举根节点时,所有的用户线程都会被被暂停,因为在根节点枚举过程中,为了保证分析结果的准确性,需要保证根节点的引用关系不会发生变化。即根节点的枚举必须在一个能保障内存一致性的快照中。
为了避免在查找引用链的过程中从上到下一个不漏的检查所有执行完的上下文和全局引用的位置这一耗时耗力的情况出现,在类加载完成后, HotSpot 会把对象内什么偏移量上是什么数据类型全部计算出来,并将引用在栈里和寄存器里的位置记录在 OopMap 中。
OopMap 虽然会帮助 HotSpot 迅速完成根节点枚举,但是 HotSpot 并不会为了每一条指令都生成一个 OopMap,因为这样会耗费大量的内存空间。HopSpot 只会在关键的节点生成 OopMap,用来记录引用信息,这些关键的节点称之为 安全点。
在进行垃圾收集时,要求必须执行到安全点处才可以暂停。
安全点的设定既不能使收集器等待太长时间,也不能过于频繁,过于频繁会增大系统负担。
抢先中断 当发生垃圾收集时,会要求线程首先中断,然后看线程是否在安全点上,如果有线程不在安全点上,则让该线程继续执行,过一会再中断,直到该线程到达安全点上
主动中断 设立一个标志位,让运行中的线程主动轮询该该标志,一旦返现该中断标志为真时,则会在最近的安全点上自动挂起。
对于运行中的线程可以使之到达安全点,但是对于休眠或者阻塞起来的线程则无法使之运行到达安全点。此时 HotSpot 便引入的安全区域。
安全区域是指能确保在某一段代码片段之中,引用关系不会发生变化,因此在该区域内,任意地方开始的垃圾收集是安全的。
当线程执行到安全区域时会首先标识自己进入到了安全区域,这样在垃圾收集时就不会管在安全区域里面的线程了。
在枚举根节点时,安全区域里的线程无法离开安全区域。
记忆集是一种用于记录从非收集区域指向收集区域的指针的数据集合。记忆集的出现是为了解决对象跨代引用所带来的问题。
在垃圾收集时,只需要判断某一块非收集区域的内存是否含有指向收集区域的内存即可。也就是说记忆集并非要将每一个指针都记录下来,因此记忆集也有精度之分。
记忆集的精度有:
使用卡精度的记忆集称之为卡表,被映射的一块内存区域称之为卡页。
在有其他分区元素引用该分区对象时,卡表元素就会变脏,变脏的时间点就在引用字段类型赋值的那一刻。
卡表元素维护的方式是写屏障,写屏障可以看做是虚拟机层面对于“引用字段类型赋值”操作的 AOP 切面,赋值前的动作称为“写前屏障”,复制后的动作称为“写后屏障”。
在根节点枚举结束后,接下来要做的就是从根节点遍历对象图,而该阶段会和用户线程并发运行。
无论采用什么样的算法都是要对对象进行标记的,一般采用三色标记法。该方法依据是否访问过对象来对对象进行标记:
一般来说,经历过一次遍历后,标记为白色的对象便是需要回收的对象,但是由于遍历过程是与用户线程并发运行的,用户线程会在运行过程中修改对象引用的关系,这样便会导致“对象消失” 问题,即将原本已存活的对象标记为消亡。
将存活的对象标记为消亡,其实就是本应为黑色的对象被标记为白色,导致该情况发生的步骤一般有两个:
解决“对象消失” 的方法: