Java-JVM-安全点SafePoint

Java-JVM-安全点SafePoint

转载声明:

本文系转载自以下文章:

转载仅为方便学习查看,一切权利属于原作者,本人只是做了整理和排版,如果带来不便请联系我删除。

摘要

本文主要是搜集网络上的好文来记录下安全点概念。看了 才发现自己是有多无知,还有很多东西都不清楚,需要多学习啊。

0x01 安全点是什么

OpenJDK官方定义如下:

  • 安全点是在程序执行期间的所有GC Root已知并且所有堆对象的内容一致的点。
  • 从全局的角度来看,所有线程必须在GC运行之前在安全点阻塞。 (作为一种特殊情况,运行JNI代码的线程可以继续运行,因为它们只使用句柄。但在安全点期间,它们必须阻塞而不是加载句柄的内容。)
  • 从本地的角度来看,安全点是一个显着的点,它位于执行线程可能阻止GC的代码块中。 大多数调用点都能当做安全点。
  • 在每个安全点都存在强大的不变量永远保持true不变,而在非安全点可能会被忽视。 编译的Java代码和C / C ++代码都在安全点之间进行了优化,但跨安全点时却不那么优化。 JIT编译器在每个安全点发出GC映射。 VM中的C / C ++代码使用程式化的基于宏的约定(例如,TRAPS)来标记潜在的安全点。

总的来说,安全点就是指,当线程运行到这类位置时,堆对象状态是确定一致的,JVM可以安全地进行操作,如GC,偏向锁解除等。

0x02 安全点分类

本节摘自https://www.zhihu.com/question/29268019/answer/43762165,作者:RednaxelaFX

  • GC safepoint需要知道在那个程序位置上,调用栈、寄存器等一些重要的数据区域里什么地方包含了GC管理的指针;
    如果要触发一次GC,那么JVM里的所有Java线程都必须到达GC safepoint。
  • Deoptimization safepoint需要知道在那个程序位置上,原本抽象概念上的JVM的执行状态(所有局部变量、临时变量、锁,等等)到底分配到了什么地方,是在栈帧的具体某个slot还是在某个寄存器里,之类的。
    如果要执行一次deoptimization,那么需要执行deoptimization的线程要在到达deoptimization safepoint之后才可以开始deoptimize。HotSpot中,安全点位置主要在:
    1. 方法返回之前
    2. 调用某个方法之后
    3. 抛出异常的位置
    4. 循环的末尾
      为什么把这些位置设置为jvm的安全点呢,主要目的就是避免程序长时间无法进入safepoint,比如JVM在做GC之前要等所有的应用线程进入到安全点后VM线程才能分派GC任务 ,如果有线程一直没有进入到安全点,就会导致GC时JVM停顿时间延长,比如写了一个超大的循环导致线程一直没有进入到安全点,GC前停顿了8秒。

0x03 安全点位置

本节摘自https://www.zhihu.com/question/29268019/answer/43762165,作者:RednaxelaFX

无论是哪种安全点,最简洁的定义是“A point in program where the state of execution is known by the VM”。不同JVM实现会选用不同的位置放置安全点。

  • 以HotSpot VM为例,在解释器里每条字节码的边界都可以是一个安全点,因为HotSpot的解释器总是能很容易的找出完整的“state of execution”。

  • 而在JIT编译的代码里,HotSpot会在所有方法的临返回之前,以及所有非counted loop的循环的回跳之前放置安全点。
    HotSpot的JIT编译器不但会生成机器码,还会额外在每个安全点生成一些“调试符号信息”,以便VM能找到所需的“state of execution”。

    • 为GC生成的符号信息是OopMap,指出栈上和寄存器里哪里有GC管理的指针;
    • 为deoptimization生成的符号信息是debugInfo,指出如果要把当前栈帧从compiled frame转换为interpreted frame的话,要从哪里把相应的局部变量、临时变量、锁等信息找出来。

之所以只在选定的位置放置安全点是因为:

  • 挂在安全点的调试符号信息要占用空间。如果允许每条机器码都可以是安全点的话,需要存储的数据量会很大(当然这有办法解决,例如用delta存储和用压缩)
  • 安全点会影响优化。特别是deoptimization 安全点,会迫使JVM保留一些只有解释器可能需要的、JIT编译器认定无用的变量的值。本来JIT编译器可能可以发现某些值不需要而消除它们对应的运算,如果在安全点需要这些值的话那就只好保留了。这才是更重要的地方,所以要尽量少放置安全点
  • 像HotSpot VM这样,在安全点会生成polling代码询问VM是否要“进入安全点”,polling也有开销所以要尽量减少。

还有一种情况是当某个线程在执行native函数的时候。此时该线程在执行JVM管理之外的代码,不能对JVM的执行状态做任何修改,因而JVM要进入安全点不需要关心它。所以也可以把正在执行native函数的线程看作“已经进入了安全点”,或者把这种情况叫做“在safe-region里”。JVM外部要对JVM执行状态做修改必须要通过JNI。所有能修改JVM执行状态的JNI函数在入口处都有安全点检查,一旦JVM已经发出通知说此时应该已经到达安全点,就会在这些检查的地方停下来把控制权交给JVM。

换一个JVM说,JRockit选择放置安全点的地方在方法的入口以及循环末尾回跳之前,跟HotSpot略为不同。

0x03 安全点工作机制

在HotSpot虚拟机中,安全点协议是主动协作的 。每一个用户线程在安全点上都会检测一个标志位,来决定自己是否暂停执行。

对于JIT编译后的代码,JIT会在代码特定的位置(通常来说,在方法的返回处和counted loop的结束处)上插入安全点代码。对于解释执行的代码,JVM会设置一个2字节的dispatch tables,解释器执行的时候会经常去检查这个dispatch tables,当有安全点请求的时候,就会让线程去进行安全点检查。

0x04 安全点总结

  1. 一个线程可以在SafePoint上,也可以不在SafePoint上。一个线程在SafePoint时,它的状态可以安全地其他JVM线程所操作和观测;不在SafePoint时,就不能。
  2. 在SafePoint上不代表被阻塞(比如:JNI方法就可以在SafePoint上运行),但是被阻塞一定发生在SafePoint上。
  3. 当JVM决定达到一个全局的SafePoint(也叫做Stop the World)时,JVM里面所有的线程都要在SafePoint上并且不能离开,直到JVM让线程允许为止。这对要求所有线程都要被良好的描述的操作(比如CG,代码反优化等等)非常有好处。
  4. 一些JVM可以持有一些私有的线程到SafePoint上而不需要全局的SafePoint,比如Zing.
  5. 当你写一些非安全的代码的时候,你必须假设SafePoint有可能发生在任何两个字节码之间。
  6. 非安全代码的调用并不要求必须有安全点,但是他们可以包含一个或者多个安全点。
  7. 所有类型的JVM有一些效率非常高的技巧和去快速的穿过SafePoint,线程并不需要真正地进入SafePoint除非虚拟机指示线程这么做。
  8. 所有的JNI方法都在SafePoint上执行。在安全点,JNI代码都不能改变和观测发起调用JNI代码线程的java机器状态。任何通过JNI API改变和观测调用线程的状态必须在调用线程离开安全点之后,以及再次进入SafePoint之前的时间内发生。

0x05 安全点应用场景

  1. GC STW
  2. 偏向锁释放
  3. 等等

0x06 更多文章

Hotspot GC研究- GC安全点 (Safepoint&Stop The World)

GC safe-point (or safepoint) and safe-region

JVM源码分析之安全点safepoint

0xFF 参考文档

HotSpot术语解释

RednaxelaFX对安全点的解释

JVM安全点介绍

Java Safepoint 与Stop The World

猜你喜欢

转载自blog.csdn.net/baichoufei90/article/details/85097727