JVM 安全点的理解

155 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

安全点(Safe Point)

在应用运行的过程中,能导致OOP Map改变的情况有很多,若每一次就去生成对应的OOPMap,那就会占用大量的内存空间,所以我们需要引入安全点的概念

安全点,就是在当程序执行在这条指令时,程序的状态已经确定,再往下走在短暂时空内不会再对栈帧内的的对象引用等造成影响,所以OOP Map只会在安全点的时候进行生成,当线程执行到安全点这个特殊区域时,线程的状态应该是稳定的,没用对象引用的改变,使用了哪些对象,内存也不会变化
所以安全点位置的选取基本上是以“是否具有让程序长时间执行的特征”为标准进行选定的,例如方法调用、循环跳转、异常跳转 等 (这段摘自书籍《深入理解Java虚拟机第三版)
通俗的讲就是以下几种状态:

  • 所有的非计数循环的末尾
  • 所有方法返回之前
  • 每条 Java 编译后的字节码的边界
public void test(){
    //栈引用
    Object o1 = new Object();
    Object o2 = new Object();
    //对象内引用
    o1.setObj(new Object());

    //进入安全点
    return;
}

安全点的数量不能太多也不能太少

  • 太多会频繁生成OOP Map造成性能损耗
  • 太少会导致线程进入安全点的时间变长,收集器等待的时间也会变长

当gc开始时,如何让所有用户线程到达安全点并停顿,提供了两种解决方案

  • 抢先式中断(Preemptive Suspension)
    在gc时,系统先中断所有用户线程,若发现有线程不在安全点上,则执行它直到它到最近的安全点上

  • 主动式中断(Voluntary Suspenstion)
    设置一个中止标志不停轮询,若标志为真时,线程就自行寻找到最近的安全点更新OOP Map后自行暂停等待gc执行

现在的虚拟机基本采用主动式中断,如HotSpot

那么主动式中断是如何轮询安全点的呢? 我们先来理一下线程执行到进入安全点挂起等待gc的过程

graph TD
线程runing --> 不断轮询 --> 若为标志为真  --> 不在安全点上则前往安全点

重点就是不断轮询的过程,每个线程都需要去轮询标志,处理不好也会造成一定的内存空间使用,所以HotSpot使用了内存保护陷阱的方式来完成安全点轮询和线程中断的功能

下面是HotSpot生成的轮询指令,线程不停执行该指令,当需要线程中断时,虚拟机将0x160100的内存页设置为不可读(标志为真),当线程指定到test指令时,就会产生自陷异常信号,那么虚拟机预先注册的异常处理器则会挂起当前线程

image.png

那么还有一种情况呢?如果在gc发起前线程已经处于sleep或者blocked状态了呢?那是要等他脱离挂起状态主执行到安全点还是放任不管趁机gc呢?这就要引入安全区域的概念了