JVM HotSpot 可达性分析算法实现细节
作者:互联网
本文部分摘自《深入理解 Java 虚拟机第三版》
根节点枚举
在之前关于可达性分析算法的介绍中我们讲过,我们需要先找出可固定作为 GC Roots 的节点,然后沿着引用链去寻找那些无用的垃圾对象。GC Roots 节点一般在全局性引用(例如常量和类静态属性)与执行上下文(例如栈帧中的本地变量表)中,尽管目标明确,但查找过程要做到高效并非一件易事,若要逐个查找可作为起源的引用肯定需要消耗不少时间
迄今为止,所有收集器在根节点枚举这一步骤时都是必须暂停用户线程,也即 Stop The World,因为如果在分析过程中出现根节点集合中对象的引用关系仍在不断变化的情况,分析结果的准确性也就无法保证了
在对栈内存进行分析时,虚拟机会看哪些位置存储了 Reference 类型,如果发现某个位置确实存的是 Reference 类型,就意味着它所引用的对象这一次不能被回收。但问题是,栈帧的本地变量表里面只有一部分数据是 Reference 类型的,那些非 Reference 类型(基本数据类型)的数据对我们毫无用处,但我们还是不得不对整个栈全部扫描一遍,这是对时间和资源的一种浪费。在 HotSpot 的解决方案中采用了一组称为 OopMap 的数据结构来实现直接找到对象引用,一旦类加载动作完成,HotSpot 就会把栈中代表引用的位置全部记录下来,这样收集器在扫描时就可以直接得知这些消息了
安全点
尽管有了 OopMap,但如果引用关系经常变化,虚拟机就需要为每一条指令都生成对应的 OopMap,这将会占用大量的额外存储空间
HotSpot 当然没那么笨,它只会在特定的位置去记录这些信息,这些位置被称为安全点(SafePoint)。有了安全点的设定,用户程序就必须执行到安全点才能暂停,而不是在代码指令流的任意位置随意停顿。安全点的选定不能太少,让收集器等待时间过长,也不能太频繁,导致增大运行时内存负担。安全点的位置选定基本上是以“是否具有让程序长时间执行的特征”为标准进行选定,“长时间执行”的最明显特征就是指令序列的复用,例如方法调用、循环跳转、异常跳转等,只有具有这些功能的指令才能产生安全点
对于安全点,另外一个要考虑的问题就是,如何在垃圾收集发生时让所有线程都跑到最近的安全点。一般有两种方案可供选择:
- 抢先式中断:垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地点不在安全点上,就恢复该线程执行,直至跑到安全点再中断。现实中几乎没有虚拟机会采用抢先式中断
- 主动式中断:垃圾收集发生时,不直接对线程操作,而是设置一个标志位,各个线程在执行时会不停地主动去轮询这个标志,一旦发现标志位为真就在最近的安全点主动中断
安全区域
安全点看似解决了我们遇到的问题,但还有一个需要思考的点:如果某一个用户线程正好处于“不执行”状态该怎么办?所谓“不执行”就是没有分配处理器时间片,典型的场景如用户线程处于 Sleep 或 Blocked 状态,这时线程无法响应中断请求,自然也就不能走到安全点主动挂起自己,而虚拟机也不可能持续等待线程重新被分处理器时间片。对于这种情况,就需要引入安全区域(Safe Region)来解决
安全区域是指能够确保在某一代码片段中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作是被扩展拉伸了的安全点
当用户线程执行到安全区域时,首先会标识自己已经进入安全区域,那样当这段时间里虚拟机要发起垃圾收集时就不必去管这些已经声明自己在安全区域内的线程了。当线程要离开安全区域时,会检查虚拟机是否已经完成了根节点枚举,如果完成了,就继续执行,否则一直等待,直到收到可以离开安全区域的信号为止
标签:虚拟机,HotSpot,节点,安全,区域,线程,引用,JVM,可达性 来源: https://www.cnblogs.com/Yee-Q/p/14193387.html