例如,一个方法中有10000次循环。当它运行1000次时,backingge_counter会触发JIT
编译。解释器继续执行。当它循环 4000 时,JIT
编译完成。
我的问题是,如何通过解释器执行余数6000 次或执行本机代码?或者在下次调用此方法之前不会执行本机代码? 下次调用此方法时会发生什么?
答案 0 :(得分:9)
假设您正在询问HotSpot JVM,答案是剩余的交互将在已编译的代码中执行。
HotSpot JVM有一种称为“堆栈内替换”的技术,可以在方法运行时从解释器切换到编译代码。
http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
堆叠更换
也称为'OSR'。转换的过程 解释(或不太优化)堆栈帧到编译(或更多 优化的)堆栈框架。当解释器发现时会发生这种情况 一个方法循环,请求编译器生成一个特殊的 nmethod在循环中的某个入口点(具体地说,在a 向后分支),并将控制转移到该nmethod。粗略 与去优化相反。
如果您使用-XX:+PrintCompilation
标志运行JVM,则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编译之后,常规编译也会排队,因此下次调用该方法时,它将直接以编译模式运行。
187 32 % 3 LoopTest::myLongLoop @ 13 (43 bytes)
187 33 3 LoopTest::myLongLoop (43 bytes)
但是,如果在再次调用该方法时常规编译未完成,则该方法将在解释器中开始运行,然后将切换到循环内的OSR条目。
答案 1 :(得分:2)
让我们重申一下这个问题:
Java HotSpot编译器能否在执行过程中将方法从解释更改为已编译?
我认为可以。
这个引擎的任务并不容易(我在几年前为PalmOS掌上电脑工作时称为JUMP的Ahead-of-Time编译器时收集了该领域的一些经验)。当引擎决定切换时,它必须考虑以下几点,至少:
程序计数器在哪里?在解释代码中,它位于从方法开头偏移的某个字节码处,确切地知道接下来要执行的字节码。在优化的本机代码中,通常JVM字节码不会转换为机器指令的隔离块,而是相互依赖,重新排列无序等等。因此,切换时可能没有与字节码程序计数器完全对应的本机指令地址。
数据在哪里?解释器(可能)将所有内容保存在堆栈中,优化的本机代码使用寄存器和堆栈分配的混合,这对于本机转换中的不同位置将是不同的。
所以我读了the HotSpot whitepaper。它没有明确地回答这个问题,但在“去优化”下有一个暗示。当一个新类被动态加载或甚至在调试会话中被替换时,之前的优化(如内联)可能会变得无效。
因此,Java HotSpot VM必须能够动态地去优化 (如有必要,再重新优化)先前优化的热点, 即使在执行热点代码时也是如此。
这也是在编译代码和解释代码之间切换,只是反过来。由于这是HotSpot引擎的记录行为,我得出的结论是,可以在当前正在执行的方法调用中从解释代码切换到已编译代码。
编辑:
我对我所理解的问题的核心不够明确。
我知道有一种方法用10000次迭代进行循环,如下所示:
void loop() {
for (int i=0; i<10000; i++) {
// example loop body
objects[i].doSomething();
}
}
例如4000次迭代HotSpot编译器优化了该方法。那么会发生什么?
有两个方面,一个是微不足道的,一个是复杂的:
微不足道的是,在循环内发生的调用(例如doSomething()
)将在可用时立即调用其编译版本。我在原来的答案中没有提到这一点,因为我认为这是理所当然的。
复杂的方面是:当前正在运行的loop()
执行是否会在i = 4000时从解释转换为编译代码?这就是我所理解的OP的问题。