什么是安全点?
在 JVM 中如何判断对象可以被回收 一文中,我们知道 HotSpot 虚拟机采取的是可达性分析算法。即通过 GC Roots 枚举判定待回收的对象。
那么,首先要找到哪些是 GC Roots。
有两种查找 GC Roots 的方法:
一种是遍历方法区和栈区查找(保守式 GC)。
一种是通过 OopMap 数据结构来记录 GC Roots 的位置(准确式 GC)。
很明显,保守式 GC 的成本太高。准确式 GC 的优点就是能够让虚拟机快速定位到 GC Roots。
对应 OopMap 的位置即可作为一个安全点(Safe Point)。
在执行 GC 操作时,所有的工作线程必须停顿,这就是所谓的”Stop-The-World”。
为什么呢?
因为可达性分析算法必须是在一个确保一致性的内存快照中进行。如果在分析的过程中对象引用关系还在不断变化,分析结果的准确性就不能保证。
安全点意味着在这个点时,所有工作线程的状态是确定的,JVM 就可以安全地执行 GC 。
如何选定安全点?
安全点太多,GC 过于频繁,增大运行时负荷;安全点太少,GC 等待时间太长。
一般会在如下几个位置选择安全点:
1、循环的末尾
2、方法临返回前
3、调用方法之后
4、抛异常的位置
为什么选定这些位置作为安全点:
主要的目的就是避免程序长时间无法进入 Safe Point。比如 JVM 在做 GC 之前要等所有的应用线程进入安全点,如果有一个线程一直没有进入安全点,就会导致 GC 时 JVM 停顿时间延长。比如这里,超大的循环导致执行 GC 等待时间过长。
如何在 GC 发生时,所有线程都跑到最近的 Safe Point 上再停下来?
主要有两种方式:
抢断式中断:在 GC 发生时,首先中断所有线程,如果发现线程未执行到 Safe Point,就恢复线程让其运行到 Safe Point 上。
主动式中断:在 GC 发生时,不直接操作线程中断,而是简单地设置一个标志,让各个线程执行时主动轮询这个标志,发现中断标志为真时就自己中断挂起。
JVM 采取的就是主动式中断。轮询标志的地方和安全点是重合的。
安全区域又是什么?
Safe Point 是对正在执行的线程设定的。
如果一个线程处于 Sleep 或中断状态,它就不能响应 JVM 的中断请求,再运行到 Safe Point 上。
因此 JVM 引入了 Safe Region。
Safe Region 是指在一段代码片段中,引用关系不会发生变化。在这个区域内的任意地方开始 GC 都是安全的。
线程在进入 Safe Region 的时候先标记自己已进入了 Safe Region,等到被唤醒时准备离开 Safe Region 时,先检查能否离开,如果 GC 完成了,那么线程可以离开,否则它必须等待直到收到安全离开的信号为止。
参考深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)