现代JVM中的Safe Region和Safe Point到底是如何定义和划分的?

软件工程师、主攻高级编程语言虚拟机的设计与实现

144 👍 / 35 💬

问题描述

现代JVM如Hotspot、J9,是如何定义safe region的,程序运行时是怎么知道自己处于安全位置,很疑惑,希望各位大神可以解答下。

首先要确认题主没有误解safe-region的概念。如果题主想像的safe-region是类似.NET的

Constrained Execution Regions

的东西的话,Java/JVM没有这种东西。

然后,关于Java/JVM的safepoint / safe-region,李晓峰(Xiao-Feng Li)有篇博文解释得很清楚,我觉得我不必多赘言了:

GC safe-point (or safepoint) and safe-region | Xiao-Feng Li

(链接是好的,咳咳)

Cliff Click大神最近的一篇博文也正好提到了safepoint:

0xdata - How does Java Both Optimize Hot Loops and Allow Debugging

不过如果要听我的版本的话,请继续读下去:

其实在高度优化的现代JVM里,safepoint有几种不同的用法。GC safepoint是最常见、大家听说得最多的,但还有deoptimization safepoint也很重要。

在HotSpot VM里,这两种safepoint目前实现在一起,但其实概念上它们俩没有直接联系,需要的数据不一样。

无论是哪种safepoint,最简洁的定义是“A point in program where the state of execution is known by the VM”。这里“state of execution”特意说得模糊,是因为不同种类的safepoint需要的数据不一样。

GC safepoint需要知道在那个程序位置上,调用栈、寄存器等一些重要的数据区域里什么地方包含了GC管理的指针;

Deoptimization safepoint需要知道在那个程序位置上,原本抽象概念上的JVM的执行状态(所有局部变量、临时变量、锁,等等)到底分配到了什么地方,是在栈帧的具体某个slot还是在某个寄存器里,之类的。

如果要触发一次GC,那么JVM里的所有Java线程都必须到达GC safepoint;

如果要执行一次deoptimization,那么需要执行deoptimization的线程要在到达deoptimization safepoint之后才可以开始deoptimize。

不同JVM实现会选用不同的位置放置safepoint。

以HotSpot VM为例,

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

而在JIT编译的代码里,HotSpot会在所有方法的临返回之前,以及所有非counted loop的循环的回跳之前放置safepoint。

HotSpot的JIT编译器不但会生成机器码,还会额外在每个safepoint生成一些“调试符号信息”,以便VM能找到所需的“state of execution”。

为GC生成的符号信息是OopMap,指出栈上和寄存器里哪里有GC管理的指针;

为deoptimization生成的符号信息是debugInfo,指出如果要把当前栈帧从compiled frame转换为interpreted frame的话,要从哪里把相应的局部变量、临时变量、锁等信息找出来。

之所以只在选定的位置放置safepoint是因为:

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

JVM外部要对JVM执行状态做修改必须要通过JNI。所有能修改JVM执行状态的JNI函数在入口处都有safepoint检查,一旦JVM已经发出通知说此时应该已经到达safepoint就会在这些检查的地方停下来把控制权交给JVM。

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