我可以从JVM specification看到这段代码:
void spin() {
int i;
for (i = 0; i < 100; i++) {
; // Loop body is empty
}
}
应汇编成:
0 iconst_0
1 istore_1
2 goto 8
5 iinc 1 1
8 iload_1
9 bipush 100
11 if_icmplt 5
14 return
其中条件检查if_icmplt
在循环体之后,但是当我自己编译并使用javap查看时,我看到:
0: iconst_0
1: istore_1
2: iload_1
3: bipush 100
5: if_icmpge 14
8: iinc 1, 1
11: goto 2
14: return
并且循环条件在循环体之前。为什么会这样?
在身体之后放置条件阻止我们在每个周期之后做goto并且对我来说是合乎逻辑的。那么为什么OracleJDK会采取另一种方式呢?
答案 0 :(得分:2)
这不是为了更好的JIT优化 - 对于JIT,这些代码片段是等效的。这是因为在javac中进行优化是没有意义的,因为JIT优化无论如何都更强大。
答案 1 :(得分:2)
实现for循环(来自其JLS语义)的简单方法就是这样:
这实际上就是编译器在你的情况下生成的内容:
初始化
0: iconst_0
1: istore_1
条件 - 如果为false,请转到6.
2: iload_1
3: bipush 100
5: if_icmpge 14
循环体(空)
递增
8: iinc 1, 1
转到2
11: goto 2
端
14: return
JVM规范中的版本是实现它的另一种方式,这也是可以接受的。 正如回答者所说,普通虚拟机的JIT编译器会再次查看并优化它(如果足够相关),所以对字节码级别的轻微优化只对没有JIT的JVM有效。按指令解释字节码指令。即便如此,任何优化都将特定于实际机器。
答案 2 :(得分:1)
它的指令数量相同,并且已经指出,规范并没有将编译器绑定到这个特定的字节码:
编译器可能编译spin到...
编译器几乎可以选择将其编译为它想要的任何字节码,只要最终效果相同。
在身体之后放置条件阻止我们在每个周期之后做goto并且对我来说是合乎逻辑的。那么为什么OracleJDK会采取另一种方式呢?
除非编写那个编译器的那个人摇摆不定,否则可能无法说清楚 - 但是我想象你的理论可能是正确的,因为似乎有可能帮助后来的JIT优化。我唯一的预感(可能是不正确的)是它可能与goto
命令的定位有关 - 如果将前6条指令作为逻辑块,可能会有更多可能更好的代码内联,没有任何东西,因为它们在编译器实际产生的字节码中。当然,由于特定goto
只能跳到该块内,因此没有逻辑差异,JIT仍然可以用完全相同的效果内联它。这些天我想象它能够巧妙地解决这个问题,但它可能并不总是如此,在这种情况下,生成的代码提供了一个很好的解决方案。当然,当(如果)JIT变得足够聪明时,就没有必要更改代码了,所以它就卡住了。
一个精心设计的理论,也许完全错误 - 但如果它是一个功能差异,这是我最好的猜测!
另一种可能性是,编译器的这一部分是如何编写的,完全是巧合的,根本就没有固定的理由(因为编译器开发人员可能没有&# 39; t 尝试使代码与示例中的代码完全相同。)