安全点 SafePoint

安全点

HotSpot 虚拟机并非在代码指令流的任意位置都能停下来开始垃圾回收的,而是强制要求必须执行到安全点后才能够暂停

安全点的选定基本上是以“是否具有让程序长时间执行的特征” 为标准进行选定的,最明显的特征是指令系列的复用,所以只有具有这些功能的指令才会产生安全点

  • 方法调用
  • 循环跳转
  • 异常跳转

循环

HotSpot 虚拟机为了避免安全点过多带来过重的负担,对循环有一项优化措施,认为循环次数较少的话,执行时间应该也不会太长

  • 使用 int 或者更小范围的数据类型作为索引的循环默认是不会放置安全点的,这种循环被称为可数循环;
  • 使用 long 或者范围更大的数据类型作为索引值的循环就被称为不可数循环(Uncounted Loop)将会被放置安全点

native方法(JNI)

当某个线程在执行 native 函数的时候,此时该线程在执行 JVM 管理以外的代码,不能对 JVM 的执行状态做任何修改,因而 JVM 要进入 SafePoint,所以也可以把正在执行 native 函数的线程看做 “已经进入了 SafePoint” 或者把这种情况叫做 “在 safe-region 里”

Thread.sleep(0) 方法就是一个 native 方法,是一个 JNI 调用,在返回 Java 这边的时候会进入 SafePoint

RocketMQ 的源码中 Thread.sleep(0) 的操作就是让他进入安全点

image.png

GC的时机

HotSpot会在所有方法的临返回之前,以及所有非counted loop的循环的回跳之前放置安全点

JVM 在做 GC 之前要等所有的应用线程进入到安全点之后 VM 线程才能分派 GC 任务,如果有线程一直没有进入到安全点,就会导致 GC 时JVM 停顿时间延长

1
forint i=0;i<1000000000;i++)

是一个典型的 Counted Loops 就是有明确的循环计数器变量,而且该变量有明确的起始值、终止值、步进长度的循环所以当这个循环运行时,JVM将无法停止线程

GC要等所有线程进入安全点的例子

  1. 启动了两个长的、不间断的循环(内部没有安全点检查)。
  2. 主线程进入睡眠状态 1 秒钟。
  3. 在1000 ms之后,JVM尝试在Safepoint停止,以便Java线程进行定期清理,但是直到可数循环完成后才能执行此操作。
  4. 主线程的 Thread.sleep 方法从 native 返回,发现安全点操作正在进行中,于是把自己挂起,直到操作结束。
    由于VMThread的某些操作需要STW,主线程在sleep结束前进入了JVM全局安全点,然后主线程要等待其他线程全部进入安全点,所以主线程被长时间没有进入安全点的其他线程给阻塞了

image.png


安全点 SafePoint
http://showyoubug.cn/2024/07/17/安全点SafePoint/
作者
Dong Su
发布于
2024年7月17日
许可协议