关于 HotSpot JVM JIT 的困惑
Confusion about HotSpot JVM JIT
例如,method.When中的10000次循环运行1000次,backedge_counter触发JIT
编译。解释器继续执行。当它循环 4000 次时,JIT
编译完成。
我的问题是,remainder 6000次是如何执行的,是解释器执行的,还是执行native代码的?还是等下次调用这个方法时才执行native代码?
下次调用此方法时会发生什么?
让我们重述一下问题:
Java HotSpot 编译器是否能够在执行过程中将方法从解释更改为编译?
我觉得可以。
引擎的任务并不容易(几年前,我在为 PalmOS 手持设备开发名为 JUMP 的 Ahead-of-Time 编译器时收集了该领域的一些经验)。当引擎决定切换时,至少要考虑以下几点:
程序计数器在哪里?在解释代码中,它位于距方法开头的某个字节码偏移处,确切地知道接下来要执行哪个字节码。在优化的本机代码中,通常 JVM 字节码不会转换为独立的机器指令块,而是相互依赖、乱序重新排列等等。因此在切换时可能没有与字节码程序计数器完全对应的本机指令地址。
数据在哪里?解释器(可能)将所有内容保存在堆栈中,优化的本机代码使用寄存器和堆栈分配的混合,在本机翻译的不同位置会有所不同。
所以我读了the HotSpot whitepaper。它没有明确回答问题,但在 "deoptimization" 下有提示。当一个新的 class 在调试会话中被动态加载甚至替换时,之前的优化(如内联)可能会变得无效。
Therefore the Java HotSpot VM must be able to dynamically deoptimize
(and then reoptimize, if necessary) previously optimized hot spots,
even while executing code for the hot spot.
这也是编译代码和解释代码之间的切换,只是反过来而已。由于这是 HotSpot 引擎的记录行为,我得出的结论是,在当前执行的方法调用中从解释代码切换到编译代码是可能的。
编辑:
我对问题核心的理解不够明确。
我了解到有一种方法执行 10000 次迭代的循环,如下所示:
void loop() {
for (int i=0; i<10000; i++) {
// example loop body
objects[i].doSomething();
}
}
例如HotSpot 编译器对该方法进行了 4000 次迭代优化。然后会发生什么?
有两个方面,一个琐碎,一个复杂:
最简单的一点是循环内发生的调用(例如doSomething()
)将在其编译版本可用时立即调用。我在原来的回答中没有提到这一点,因为我认为这是理所当然的。
复杂的方面是:currently-运行 loop()
执行是否会在 i=4000 处从解释代码切换到编译代码?这就是我所理解的OP的问题。
假设您询问的是 HotSpot JVM,答案是剩余的交互将在 编译 代码中执行。
HotSpot JVM 有一种称为 'on-stack replacement' 的技术,可以在方法 运行ning 时从解释器切换到编译代码。
http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
on-stack replacement
Also known as 'OSR'. The process of converting an
interpreted (or less optimized) stack frame into a compiled (or more
optimized) stack frame. This happens when the interpreter discovers
that a method is looping, requests the compiler to generate a special
nmethod with an entry point somewhere in the loop (specifically, at a
backward branch), and transfers control to that nmethod. A rough
inverse to deoptimization.
如果您 运行 JVM 带有 -XX:+PrintCompilation
标志,OSR 编译将用 %
符号标记:
274 27 3 java.lang.String::lastIndexOf (52 bytes)
275 29 3 java.lang.String::startsWith (72 bytes)
275 28 3 java.lang.String::startsWith (7 bytes)
275 30 3 java.util.Arrays::copyOf (19 bytes)
276 32 4 java.lang.AbstractStringBuilder::append (29 bytes)
276 31 s 3 java.lang.StringBuffer::append (13 bytes)
283 33 % 3 LoopTest::myLongLoop @ 13 (43 bytes)
^ ^
OSR bytecode index of OSR entry
更新
通常在 OSR 编译之后还会对常规编译进行排队,以便下次调用该方法时,它将在编译模式下直接启动 运行ning。
187 32 % 3 LoopTest::myLongLoop @ 13 (43 bytes)
187 33 3 LoopTest::myLongLoop (43 bytes)
但是,如果在再次调用该方法时常规编译尚未完成,该方法将在解释器中启动 运行ning,然后在循环内切换到 OSR 入口。
例如,method.When中的10000次循环运行1000次,backedge_counter触发JIT
编译。解释器继续执行。当它循环 4000 次时,JIT
编译完成。
我的问题是,remainder 6000次是如何执行的,是解释器执行的,还是执行native代码的?还是等下次调用这个方法时才执行native代码? 下次调用此方法时会发生什么?
让我们重述一下问题:
Java HotSpot 编译器是否能够在执行过程中将方法从解释更改为编译?
我觉得可以。
引擎的任务并不容易(几年前,我在为 PalmOS 手持设备开发名为 JUMP 的 Ahead-of-Time 编译器时收集了该领域的一些经验)。当引擎决定切换时,至少要考虑以下几点:
程序计数器在哪里?在解释代码中,它位于距方法开头的某个字节码偏移处,确切地知道接下来要执行哪个字节码。在优化的本机代码中,通常 JVM 字节码不会转换为独立的机器指令块,而是相互依赖、乱序重新排列等等。因此在切换时可能没有与字节码程序计数器完全对应的本机指令地址。
数据在哪里?解释器(可能)将所有内容保存在堆栈中,优化的本机代码使用寄存器和堆栈分配的混合,在本机翻译的不同位置会有所不同。
所以我读了the HotSpot whitepaper。它没有明确回答问题,但在 "deoptimization" 下有提示。当一个新的 class 在调试会话中被动态加载甚至替换时,之前的优化(如内联)可能会变得无效。
Therefore the Java HotSpot VM must be able to dynamically deoptimize (and then reoptimize, if necessary) previously optimized hot spots, even while executing code for the hot spot.
这也是编译代码和解释代码之间的切换,只是反过来而已。由于这是 HotSpot 引擎的记录行为,我得出的结论是,在当前执行的方法调用中从解释代码切换到编译代码是可能的。
编辑:
我对问题核心的理解不够明确。
我了解到有一种方法执行 10000 次迭代的循环,如下所示:
void loop() {
for (int i=0; i<10000; i++) {
// example loop body
objects[i].doSomething();
}
}
例如HotSpot 编译器对该方法进行了 4000 次迭代优化。然后会发生什么?
有两个方面,一个琐碎,一个复杂:
最简单的一点是循环内发生的调用(例如
doSomething()
)将在其编译版本可用时立即调用。我在原来的回答中没有提到这一点,因为我认为这是理所当然的。复杂的方面是:currently-运行
loop()
执行是否会在 i=4000 处从解释代码切换到编译代码?这就是我所理解的OP的问题。
假设您询问的是 HotSpot JVM,答案是剩余的交互将在 编译 代码中执行。
HotSpot JVM 有一种称为 'on-stack replacement' 的技术,可以在方法 运行ning 时从解释器切换到编译代码。
http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
on-stack replacement
Also known as 'OSR'. The process of converting an interpreted (or less optimized) stack frame into a compiled (or more optimized) stack frame. This happens when the interpreter discovers that a method is looping, requests the compiler to generate a special nmethod with an entry point somewhere in the loop (specifically, at a backward branch), and transfers control to that nmethod. A rough inverse to deoptimization.
如果您 运行 JVM 带有 -XX:+PrintCompilation
标志,OSR 编译将用 %
符号标记:
274 27 3 java.lang.String::lastIndexOf (52 bytes)
275 29 3 java.lang.String::startsWith (72 bytes)
275 28 3 java.lang.String::startsWith (7 bytes)
275 30 3 java.util.Arrays::copyOf (19 bytes)
276 32 4 java.lang.AbstractStringBuilder::append (29 bytes)
276 31 s 3 java.lang.StringBuffer::append (13 bytes)
283 33 % 3 LoopTest::myLongLoop @ 13 (43 bytes)
^ ^
OSR bytecode index of OSR entry
更新
通常在 OSR 编译之后还会对常规编译进行排队,以便下次调用该方法时,它将在编译模式下直接启动 运行ning。
187 32 % 3 LoopTest::myLongLoop @ 13 (43 bytes)
187 33 3 LoopTest::myLongLoop (43 bytes)
但是,如果在再次调用该方法时常规编译尚未完成,该方法将在解释器中启动 运行ning,然后在循环内切换到 OSR 入口。