观察到的行为受到HotSpot优化器的影响,但不是唯一的原因。当我运行下面的代码
public static void main(String[] argv) {
System.out.println(System.getProperty("java.version"));
System.out.println(countDepth());
System.out.println(countDepth());
System.out.println(countDepth());
System.out.println(countDepth());
System.out.println(countDepth());
System.out.println(countDepth());
System.out.println(countDepth());
}
static int countDepth() {
try { return 1+countDepth(); }
catch(StackOverflowError err) { return 0; }
}
启用JIT,我得到的结果如下:
> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -cp build\classes X
1.8.0_40-ea
2097
4195
4195
4195
12587
12587
12587
> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -cp build\classes X
1.8.0_40-ea
2095
4193
4193
4193
12579
12579
12579
> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -cp build\classes X
1.8.0_40-ea
2087
4177
4177
12529
12529
12529
12529
在这里,JIT的效果是清晰可见的,显然优化的代码需要更少的堆栈空间,并且显示分层编译已启用(实际上,使用-XX:-TieredCompilation显示单个跳转,如果程序运行足够长)。
相比之下,使用禁用JIT,我得到以下结果:
> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -Xint -cp build\classes X
1.8.0_40-ea
2104
2104
2104
2104
2104
2104
2104
> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -Xint -cp build\classes X
1.8.0_40-ea
2076
2076
2076
2076
2076
2076
2076
> f:\Software\jdk1.8.0_40beta02\bin\java -Xss68k -server -Xint -cp build\classes X
1.8.0_40-ea
2105
2105
2105
2105
2105
2105
2105
这些值仍然有所不同,但不在单个运行时线程中,而且数量较小。
因此,如果优化器可以减少每个方法调用所需的堆栈空间,那么(相当小的)差异会变得更大。由于内联。
什么可以造成这样的差异?我不知道这个JVM是如何实现的,但是一个场景可能是堆栈限制被强制执行的方式需要堆栈结束地址的一定对齐(例如匹配内存页大小),而内存分配则返回内存与起始地址有较弱的一致保证。将这样的场景与ASLR组合,并且在对齐要求的大小内可能总是有差异。