代码案例
一、原始代码
public class Test {
public static void main(String[] args) {
long then = System.currentTimeMillis();
double result;
for (int i = 0; i < 1000000000; i++) {
result = i*i;
}
long now = System.currentTimeMillis();
System.out.println(now - then);
}
}
上面代码执行耗时4ms。原因是 result 没有被使用到,智能的编译器就会对代码进行优化,所以 jvm 最终执行的是以下代码:
public class Test {
public static void main(String[] args) {
long then = System.currentTimeMillis();
long now = System.currentTimeMillis();
System.out.println(now - then);
}
}
二、将结果定义为成员变量
我们将 result 定义为成员变量后,执行用时为175ms。如果JVM 将 result 存放到某个寄存器中,并且被写了多次,但是没有读取操作(因为其他线程不能读取这个寄存器),因此,除了最终结果可以优化掉所有的循环结果。
public class Test {
static double result;
public static void main(String[] args) {
long then = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
result = i*i;
}
long now = System.currentTimeMillis();
System.out.println(now - then);
}
}
三、将结果使用 volatile 修饰
将 result 使用volatile修饰后,再次执行用时 1873ms。由于 result 用 volatile 定义,确保 JVM 必须保存每次循环的计算结果,JVM 不能优化掉这些计算,因为它无法知道是否会有其他线程过来,从主存(main memory)中读取这个值。
public class Test {
volatile static double result;
public static void main(String[] args) {
long then = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
result = i*i;
}
long now = System.currentTimeMillis();
System.out.println(now - then);
}
}
验证
在《java性能权威指南》中写道,这些是由于java的编译器优化导致的。我们可以查看下原始代码的字节码。从反编译的字节码来看,编译器并没有进行优化掉没用到的循环代码,这个还需进一步验证原因。
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
3: lstore_1
4: iconst_0
5: istore 5
7: iload 5
9: ldc #3 // int 1000000000
11: if_icmpge 27
14: iload 5
16: iload 5
18: imul
19: i2d
20: dstore_3
21: iinc 5, 1
24: goto 7
27: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
30: lstore 5
32: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
35: lload 5
37: lload_1
38: lsub
39: invokevirtual #5 // Method java/io/PrintStream.println:(J)V
42: return
}