观察到的差异是由 后台JIT编译 引起的。
该过程如下所示:
- 方法
f()
开始在解释器中执行。 - 在多次调用(大约250次)之后,该方法被安排进行编译。
- 编译器线程与应用程序线程并行工作。同时,该方法在解释器中继续执行。
- 编译器线程完成编译后,方法入口点将被替换,因此下一个调用
f()
将调用该方法的已编译版本。
应用线程和JIT编译器线程之间基本上存在竞争。在方法的编译版本准备就绪之前,解释器可能会执行不同数量的调用。最后是解释帧和编译帧的混合。
难怪编译的框架布局与解释的框架布局不同。编译的帧通常较小;他们不需要将所有执行上下文存储在堆栈上(方法引用,常量池引用,事件探查器数据,所有参数,表达式变量等)。
此外,分层编译(JDK 8之后的默认设置)具有更多的竞赛可能性。可以有3种类型的帧组合:解释器C1和C2(请参见下文)。
让我们进行一些有趣的实验以支持该理论。
纯解释模式。没有JIT编译。
没有比赛=>稳定的结果。$ java -Xint Main
11895
11895
11895禁用 后台 编译。JIT为ON,但与应用程序线程同步。
再也没有比赛,但是由于已编译的帧,调用的数量现在更多了。$ java -XX:-BackgroundCompilation Main
23462
23462
23462执行 前 用C1编译所有内容。与以前的情况不同,堆栈上没有解释的帧,因此数量会更高。
$ java -Xcomp -XX:TieredStopAtLevel=1 Main
23720
23720
23720现在, 在 执行 之前 用C2编译所有内容。这将以最小的帧生成最优化的代码。通话次数最多。
$ java -Xcomp -XX:-TieredCompilation Main
59300
59300
59300
由于默认堆栈大小为1M,因此这意味着该帧现在只有16个字节长。是吗?
$ java -Xcomp -XX:-TieredCompilation -XX:CompileCommand=print,Main.f Main 0x00000000025ab460: mov %eax,-0x6000(%rsp) ; StackOverflow check 0x00000000025ab467: push %rbp ; frame link 0x00000000025ab468: sub $0x10,%rsp 0x00000000025ab46c: movabs $0xd7726ef0,%r10 ; r10 = Main.class 0x00000000025ab476: addl $0x2,0x68(%r10) ; Main.counter += 2 0x00000000025ab47b: callq 0x00000000023c6620 ; invokestatic f() 0x00000000025ab480: add $0x10,%rsp 0x00000000025ab484: pop %rbp ; pop frame 0x00000000025ab485: test %eax,-0x23bb48b(%rip) ; safepoint poll 0x00000000025ab48b: retq
实际上,这里的帧是32个字节,但是JIT内联了一级递归。
- 最后,让我们看一下混合堆栈跟踪。为了获得它,我们将使JVM在StackOverflowError上崩溃(调试版本中可用的选项)。
$ java -XX:AbortVMonException=java.lang.StackOverflowError Main
崩溃转储
hs_err_pid.log包含详细的堆栈跟踪,在这里我们可以在底部找到解释的帧,在中间找到C1帧,最后在顶部找到C2帧。
Java frames: (J=compiled Java pre, j=interpreted, Vv=VM pre)J 164 C2 Main.f()V (12 bytes) @ 0x00007f21251a5958 [0x00007f21251a5900+0x0000000000000058]J 164 C2 Main.f()V (12 bytes) @ 0x00007f21251a5920 [0x00007f21251a5900+0x0000000000000020] // ... repeated 19787 times ...J 164 C2 Main.f()V (12 bytes) @ 0x00007f21251a5920 [0x00007f21251a5900+0x0000000000000020]J 163 C1 Main.f()V (12 bytes) @ 0x00007f211dca50ec [0x00007f211dca5040+0x00000000000000ac]J 163 C1 Main.f()V (12 bytes) @ 0x00007f211dca50ec [0x00007f211dca5040+0x00000000000000ac] // ... repeated 1866 times ...J 163 C1 Main.f()V (12 bytes) @ 0x00007f211dca50ec [0x00007f211dca5040+0x00000000000000ac]j Main.f()V+8j Main.f()V+8 // ... repeated 1839 times ...j Main.f()V+8j Main.main([Ljava/lang/String;)V+0v ~StubRoutines::call_stub



