可达性算法主要确立根节点集合,根据根节点进行向下搜索,从而判断该对象是否被引用。但是目前所有的收集器在确定根节点这一步骤都是需要暂停线程的。根节点枚举始终还是必须在一个能保障一致性的快照中才得以进行。如果根节点集合中的对象引用关系在枚举期间还是不断变化的话,那么分析结果的准确性是无法保证的。所以枚举根节点时也是必须要停顿的。 但是java应用十分庞大,如果要直接检查所有的引用,那线程暂停的时间会十分长。 所以在hotspot中引入了oopmap的数据结构来存放对象的引用位置。
oopmap生成时机
- 类加载完成,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,
- 在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。
这样收集器在扫描时就可以直接得知这些信息了,并不需要真正 一个不漏地从方法区等GC Roots开始查找。
安全点
在oopmap的协助下,确实可以快速的完成根节点的枚举。但是由于生成oopmap需要占用额外的空间,所以虚拟机没有对每条指令都进行oopmap的生成,而是采取了特定的位置记录,也就是安全点。安全点的选定既不能太少以至于让收集器等待时间过长,也不能太过频繁以至于过分增大运行时的内存负荷。安全点位置的选取基本上是以是否具有让程序长时间执行的特征为标准进行选定的。
有了安全点后,就需要考虑何时暂停线程,一般有两个方案抢先式中断和主动式中断。
抢先式中断
抢先式中断就是不需要代码配合,在发生垃圾收集的时候,虚拟机主动暂停所有线程,同时判断用户线程是否在安全点上,若还未到安全点则恢复线程,等达到了安全点再重新中断。
主动式中断
主动式中断就是当垃圾收集器需要中断线程时,不直接中断线程,而是设计一个标志位,所有用户线程去主动轮询这个标志位,判断自己是否到达,若到达则主动中断线程。由于轮询操作会频繁出现,所以HotSpot虚拟机提供了一个内存保护陷阱,将轮询操作指令精简到只有一条汇报指令。
但是若线程处于sleep或blocked的状态,这时线程是无法响应虚拟机的中断请求,无法自己走到安全点。虚拟机也不可能等待该线程重新唤醒,再处理,此时就有了安全区域这个概念。
安全区域
安全区域是指能够确保在某一段代码片段中,引用关系不会发生变化,此时可以把这块地方看作安全区域。 当用户线程执行到安全区域的代码时,会标识自己已经进入安全区域,此时如果垃圾收集器要进行垃圾收集,就不会去管这些已经声明了安全区域的线程。当线程要离开安全区域时,要检查虚拟机是否完成了根节点枚举,若未完成则还需一直等待,直到收到了可以离开的信号为止。