给出以下代码:
public class Test{
static int[] big = new int [10000];
public static void main(String[] args){
long time;
for (int i = 0; i < 16; i++){
time = System.nanoTime();
getTimes();
System.out.println(System.nanoTime() - time);
}
}
public static void getTimes(){
int d;
for (int i = 0; i < 10000; i++){
d = big[i];
}
}
}
输出显示持续时间递减趋势:
171918
167213
165930
165502
164647
165075
203991
70563
45759
43193
45759
44476
45759
52601
47897
48325
为什么getTimes
中的相同代码在执行8次或更多次后不到三分之一的时间内被执行? (编辑:它并不总是发生在第8次,而是从第5次到第10次)
答案 0 :(得分:9)
现在看看你收到的所有评论,你应该清楚看到你所看到的是一些JIT优化的结果。但实际发生了什么以及为什么代码优化总是几乎在外部for
的相同迭代次数之后?
我会尝试回答这两个问题,但请记住,此处说明的所有内容都与Oracle的Hotspot VM相关 。没有Java规范定义JVM JIT应该如何表现。
首先,让我们看看JIT正在使用一些额外的标志来运行Test程序(普通的JVM足以运行它,不需要加载调试共享库,某些需要UnlockDiagnosticVMOptions
选项):
java -XX:+PrintCompilation Test
使用此输出完成执行(在开头删除几行,表明正在编译其他方法):
[...]
195017
184573
184342
184262
183491
189494
131 51% 3 Test::getTimes @ 2 (22 bytes)
245167
132 52 3 Test::getTimes (22 bytes)
165144
65090
132 53 1 java.nio.Buffer::limit (5 bytes)
59427
132 54% 4 Test::getTimes @ 2 (22 bytes)
75137
48110
135 51% 3 Test::getTimes @ -2 (22 bytes) made not entrant
142 55 4 Test::getTimes (22 bytes)
150820
86951
90012
91421
代码中的printlns
与JIT正在执行的编译相关的诊断信息交错。
看一行:
131 51% 3 Test::getTimes @ 2 (22 bytes)
每列的含义如下:
osr_bci
)仅保留与getTimes
相关的行:
131 51% 3 Test::getTimes @ 2 (22 bytes)
132 52 3 Test::getTimes (22 bytes)
132 54% 4 Test::getTimes @ 2 (22 bytes)
135 51% 3 Test::getTimes @ -2 (22 bytes) made not entrant
142 55 4 Test::getTimes (22 bytes)
很明显getTimes
不止一次被编译,但每次都以不同的方式编译。
%
符号表示已经执行了堆栈替换(OSR),这意味着getTimes
中包含的10k循环已经编译与方法的其余部分隔离并且JVM用编译版本替换了方法代码的部分。 osr_bci
是一个指向这个新编译的代码块的索引。
下一个编译是一个经典的JIT编译,它编译所有getTimes
方法(大小仍然相同,因为除了循环之外该方法中没有其他内容)。
第三次执行另一个OSR但处于不同的分层级别。 Java7中添加了分层编译,基本上允许JVM在运行时选择客户端或服务器 JIT模式 ,在两者之间自由切换必要。客户端模式执行一组更简单的优化策略,而服务器模式能够应用更复杂的优化,另一方面,在编译时花费更大的成本。
我不会详细介绍不同模式或分层编译,如果您需要Scott Oaks推荐Java Performance: The Definitive Guide的其他信息,还要检查this question,解释各级之间的变化。< / p>
回到PrintCompilation的输出,这里的要点是,从某个时间点开始,执行一系列复杂度增加的编译,直到方法变得明显稳定(即JIT不再编译)
那么,为什么所有这些都是在主循环的5-10次迭代之后的某个特定时间点开始的?
因为内部getTimes
循环已成为热点&#34;。
Hotspot VM,通常定义&#34; hot&#34;那些已被调用至少10k次的方法(这是历史默认阈值,可以使用-XX:CompileThreshold=<num>
进行更改,分层编译现在有多个阈值)但是在OSR的情况我猜测它是在一块代码被认为是热的时候执行的。就绝对或相对执行时间而言,方法内部包含它。
其他参考资料
Krystal Mok的答案 1 :(得分:4)
虚拟机的JIT(即时)编译器优化了Java字节代码的解释。例如,如果你有一个if()语句,在大约99%的情况下是假的,那么jit会优化你的代码以获得错误的情况,这会使你的真实案例最终变慢。抱歉英语不好。
答案 2 :(得分:0)
示例:优化前的代码
class A {
B b;
public void newMethod() {
y = b.get(); //calling get() function
...do stuff...
z = b.get(); // calling again
sum = y + z;
}
}
class B {
int value;
final int get() {
return value;
}
}
示例:优化后的代码
class A {
B b;
public void newMethod() {
y = b.value;
...do stuff...
sum = y + y;
}
}
class B {
int value;
final int get() {
return value;
}
}
最初,代码包含对b.get()方法的两次调用。后 优化时,两个方法调用被优化为一个 变量复制操作;也就是说,优化的代码不需要 执行方法调用以获取B类的字段值。