深入理解JAVA虚拟机读书笔记:HotSpot的算法实现

上一篇我们学习了判断对象是否存活的一些算法,主要就是可达性分析算法。
这篇来讲讲HotSpot的算法实现。

一、枚举根节点

我们知道在可达性分析算法中是需要有GC Roots节点,可作为GC Roots的节点主要在全局性的引用(例如常量或静态变量,这部分数据存储在方法区)与执行上下文(例如栈帧的局部变量表)中,现在很多应用仅仅方法区就有数百兆,如果要逐个检查这里面的引用,那么必然会消耗很多时间。而且在枚举GC Roots节点时,程序是需要停顿(Stop The World,会停顿Java所有执行线程)的(这项分析工作必须在一个能确保一致性的快照中进行——这里的“一致性”指在整个分析期间整个执行系统看起来就像被冻结在某个时间点上,不可以出现分析过程中对象引用关系还在不断变化的情况,这是保证分析结果准确性的基础),所以我们不可能停掉整个程序花费大量的时间去扫描方法区、栈区,那么虚拟机是是如何实现在不扫描方法区、栈区的情况下找到可作为GC Roots的对象呢?
答案是:
在HotSpot的实现中,该实现是准确式GC,使用了一组成为OopMap的数据结构,对象的类型信息里记录有自己的OopMap,记录了在该类型的对象内什么偏移量上是什么类型的数据,这些数据是在类加载过程中计算得到的;在JIT编译中,也会在特定的位置(安全点)记录下栈和寄存器中哪些地方是引用,每个方法可能会有好几个OopMap,就是根据safepoint把一个方法的代码分成几段,每一段代码一个OopMap,每一个OopMap也只是仅限于自己的那一段代码。这样,我们在GC枚举根节点时,就可以避免全栈、整个方法区来扫描来判断是否引用,只需要递归遍历每一个OopMap即可找到GC Roots。然后实行可达性分析算法。

二、安全点

我们上面提到了在JIT编译中,会在特定的位置记录下OopMap,这些特定的位置就是安全点。程序在执行时并不是在所有地方都能停顿下来开始GC的,只有到达安全点时才能暂停。安全点的选定既不能太少以致于GC等待时间过长,也不能过于频繁以致于过分增大运行时的负荷。
这些特定的位置主要在:
1、循环的末尾
2、方法临返回前/调用方法的call指令后
3、可能抛出异常的位置。
对于安全点,我们另一个需要考虑的问题是如何在GC发生时让所有线程都“跑”到安全点停顿下来。一般有两种方案可供选择:
抢占式中断:不需要线程的执行代码配合,GC发生时中断所有线程,如果发现有线程不在安全点上,那么就恢复线程,让它跑到安全点上。(现在几乎没有虚拟机在用这种方案了)。
主动式中断:线程的执行代码主动配合,虚拟机不直接对线程操作,只需要简单的设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真就将自己挂起,在安全点处轮询标志。

三、安全区域

使用Safepoint并没有完全解决如果进入GC的问题,在程序处于SLeep状态或者Blocked状态时,这个时候线程无法去轮询JVM的中断标志的去安全点挂起自己的,JVM显然也不可能等待线程重新被分配CPU时间。这种情况,我们引入了一种称作安全区域(Safe region)来解决。
安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中的任意地方开始GC都是安全的。
当线程执行到Safe Region中的代码时,首先标识自己已经进入了Safe Region,那样,当在这段时间JVM发起GC时,就不用管标识自己为Safe Region状态的线程了。在线程要离开Safe Region时,它要检查系统是否已经完成了根节点枚举,如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开Safe Region的信号位置。

猜你喜欢

转载自blog.csdn.net/f191501223/article/details/84550945