在实验期间,我发现了与堆栈溢出错误相关的非常有趣的问题。 看看这段代码:
Foo类:
public class Foo {
private int i;
public void doFoo() {
i++;
doBar();
}
public void doBar() {
doFoo();
}
public int getI() {
return i;
}
public void reset() {
i = 0;
}
}
App类:
public class App {
public static void main(String[] args) {
Foo foo = new Foo();
try {
foo.doFoo();
} catch (StackOverflowError e) {
System.out.println(foo.getI());
}
}
}
很明显,递归调用doFoo和doBar方法会导致stackOverflowError。但是在这之后foo.i会有什么价值呢?我使用默认的VM堆栈大小测试了几次 - 这是System.out.println(foo.getI());
打印的内容:
如果我们增加堆栈大小怎么办?将-Xss1m
参数添加到VM args时,结果如下:
这是一回事!我的价值仍比上次运行的价格高出4500左右!
然而,当我们改变主要方法时:
for (int i = 0; i < 10; i++) {
try {
foo.doFoo();
foo.reset();
} catch (StackOverflowError e) {
System.out.println(foo.getI());
}
}
我们将得到可预测的结果:
这可能是由于JVM初始化后越来越多的堆栈可用空间。但是,为什么在一次又一次地调用程序时会有~4500次振荡? PS。我直接从Eclipse运行这个应用程序(如果这很重要)。
====
编辑:
好的,我现在可以看到,foo.reset()
永远不会被调用,因为错误会在它之前抛出。当foo.dooFoo()
与foo.reset()
交换时,我们现在有了不变的结果:
但是,质疑为什么运行程序运行时有~4500次振荡,仍然是开放的。
== EDIT2
此问题仅与直接从Eclipse运行,不带-Xint参数有关。当程序从命令行java -cp . App
启动时,i更加恒定:+/- 5。
答案 0 :(得分:1)
我怀疑正在发生的事情(以及您的证据显示的是)方法被频繁调用,以至于您的代码最终会通过JIT编译器运行。当发生这种情况时,我推测优化可能会启动,并认识到每次执行i
时递增doFoo
都不是必需的 - 有点像展开循环。
我不完全确定为什么你会看到任何可变性,除了JIT启动时可能存在非确定性成本组件,或者偶尔它会在几帧内启动。