为什么一个含有40亿次迭代的Java循环只需要2毫秒?
2026-02-04 21:32:57 世界杯女排决赛正如已经指出的那样,JIT (即时编译器)可以优化空循环以删除不必要的迭代。但是如何实现呢?
实际上,有两个JIT编译器: C1 和 C2。首先,代码使用C1进行编译。 C1收集统计信息,帮助JVM发现我们的空循环在100%的情况下没有任何变化,并且是无用的。 在这种情况下,C2进入阶段。 当代码被非常频繁地调用时,可以使用收集的统计信息对其进行优化并使用C2进行编译。
例如,我将测试下一个代码片段(我的JDK设置为 slowdebug build 9-internal ):
public class Demo {
private static void run() {
for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
}
System.out.println("Done!");
}
}
使用以下命令行选项:
-XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*Demo.run
我的run方法有不同的版本,使用C1和C2进行编译。对我而言,最终的变体(C2)大致如下:
...
; B1: # B3 B2 <- BLOCK HEAD IS JUNK Freq: 1
0x00000000125461b0: mov dword ptr [rsp+0ffffffffffff7000h], eax
0x00000000125461b7: push rbp
0x00000000125461b8: sub rsp, 40h
0x00000000125461bc: mov ebp, dword ptr [rdx]
0x00000000125461be: mov rcx, rdx
0x00000000125461c1: mov r10, 57fbc220h
0x00000000125461cb: call indirect r10 ; *iload_1
0x00000000125461ce: cmp ebp, 7fffffffh ; 7fffffff => 2147483647
0x00000000125461d4: jnl 125461dbh ; jump if not less
; B2: # B3 <- B1 Freq: 0.999999
0x00000000125461d6: mov ebp, 7fffffffh ; *if_icmpge
; B3: # N44 <- B1 B2 Freq: 1
0x00000000125461db: mov edx, 0ffffff5dh
0x0000000012837d60: nop
0x0000000012837d61: nop
0x0000000012837d62: nop
0x0000000012837d63: call 0ae86fa0h
...
虽然有点凌乱,但是如果你仔细看,就会发现这里没有长时间运行的循环。这里有三个块:B1、B2和B3,执行步骤可以是B1 -> B2 -> B3或B1 -> B3。其中Freq: 1表示块执行的标准化估计频率。