JavaJVM_HotSpot算法
1. 根节点枚举
迄今为止,所有收集器在根节点枚举这一步骤都是必须暂停用户线程的。即便是号称停顿时间可控、或者(几乎)不会发生停顿的 CMS、G1、ZGC 等收集器,枚举根节点时也必须要停顿。
在对栈内存进行分析时,虚拟机会看哪些位置存储了 Reference 类型,如果发现某个位置确实存的是 Reference 类型,就意味着它所引用的对象这一次不能被回收。但问题是,栈帧的本地变量表里面只有一部分数据是 Reference 类型的,那些非 Reference 类型(基本数据类型)的数据对我们毫无用处,但我们还是不得不对整个栈全部扫描一遍,这是对时间和资源的一种浪费。在 HotSpot 的解决方案中采用了一组称为 OopMap 的数据结构来实现直接找到对象引用,一旦类加载动作完成,HotSpot 就会把栈中代表引用的位置全部记录下来,这样收集器在扫描时就可以直接得知这些消息了.
2. 安全点
HotSpot 当然没那么笨,它只会在
特定的位置
去记录这些信息,这些位置被称为安全点(SafePoint)
。有了安全点的设定,用户程序就必须执行到安全点才能暂停
,而不是在代码指令流的任意位置随意停顿。安全点的选定不能太少,让收集器等待时间过长,也不能太频繁,导致增大运行时内存负担。安全点的位置选定基本上是以“是否具有让程序长时间执行的特征”为标准进行选定,“长时间执行”的最明显特征就是指令序列的复用
,例如方法调用、循环跳转、异常跳转
等,只有具有这些功能的指令才能产生安全点对于安全点,另外一个要考虑的问题就是,
如何在垃圾收集发生时让所有线程都跑到最近的安全点
。一般有两种方案可供选择:
抢先式中断
:垃圾收集发生时,系统首先把所有用户线程全部中断,如果发现有用户线程中断的地点不在安全点上,就恢复该线程执行,直至跑到安全点再中断。现实中几乎没有虚拟机会采用抢先式中断
主动式中断
:垃圾收集发生时,不直接对线程操作,而是设置一个标志位,各个线程在执行时会不停地主动去轮询这个标志,一旦发现标志位为真就在最近的安全点主动中断
3. 安全区域
如果某一个
用户线程正好处于“不执行”状态该怎么办
?所谓“不执行”就是没有分配处理器时间片
,典型的场景如用户线程处于 Sleep 或 Blocked 状态
,这时线程无法响应中断请求,自然也就不能走到安全点主动挂起自己,而虚拟机也不可能持续等待线程重新被分处理器时间片。对于这种情况,就需要引入安全区域(Safe Region)来解决.
安全区域
是指能够确保在某一代码片段中,引用关系不会发生变化
,因此,在这个区域中任意地方开始垃圾收集都是安全的
。我们也可以把安全区域看作是被扩展拉伸了的安全点当用户线程执行到安全区域时,首先会
标识自己已经进入安全区域
,那样当这段时间里虚拟机要发起垃圾收集时就不必去管这些已经声明自己在安全区域内的线程了。当线程要离开安全区域时,会检查虚拟机是否已经完成了根节点枚举,如果完成了,就继续执行,否则一直等待,直到收到可以离开安全区域的信号为止