在实验过程中,我发现了与堆栈溢出错误相关的非常有趣的问题。 看一下这段代码:
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;
}
}
应用程序类别:
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 会有什么值呢?我已经使用默认虚拟机堆栈大小对其进行了几次测试 - 这就是 System.out.println(foo.getI());
打印:
- 第一次运行:4372
- 第二次运行:8364
- 第三次运行:3381
- 第四次运行:8406
- 第五次运行:3485
你能看到吗?持续运行此应用程序,i 变量将始终比上次运行大/小约 4500。
如果我们增加堆栈大小怎么办?添加时-Xss1m
参数到VM args,结果如下:
- 第一次运行:33364
- 第二次运行:38404
- 第三次运行:33787
- 第四次运行:38434
- 第五次运行:33805
都是一样的! i 值仍然比上次运行大/小约 4500!
然而,当我们将 main 方法更改为:
for (int i = 0; i < 10; i++) {
try {
foo.doFoo();
foo.reset();
} catch (StackOverflowError e) {
System.out.println(foo.getI());
}
}
我们将得到可预测的结果:
- 38398
- 80796
- 123194
- 165592
- 207990
- 250388
- 292786
- 335184
- 377582
- 419980
可能是由于JVM初始化后堆栈空闲空间越来越多。但是,为什么一次又一次地调用程序时会出现约 4500 次振荡呢? 附言。我直接从 Eclipse 运行这个应用程序(如果重要的话)。
====
编辑:
好的,我现在可以看到,foo.reset()
永远不会被调用,因为 Error 在它之前被抛出。当foo.dooFoo()
与 foo.reset()
交换,我们现在有了恒定的结果:
- 38394
- 42394
- 42394
- 42394
- 42394
- 42394
- 42394
- 42394
- 42394
- 42394
但是,问题是为什么在运行程序时有大约 4500 次振荡,仍然是开放的。
==编辑2
此问题仅与直接从 Eclipse 运行(不带 -Xint 参数)有关。当程序从命令行启动时,java -cp . App
,i 更恒定:+/- 5。
最佳答案
我怀疑正在发生的事情(以及你的证据表明)是这些方法被频繁调用,以至于你的代码最终通过 JIT 编译器运行。当这种情况发生时,我推测优化可能会启动并认识到每次执行 doFoo
时递增 i
是不必要的——有点像展开循环.
我不完全确定为什么您会看到任何变化,除了 JIT 启动时可能存在不确定的成本组件,或者偶尔会启动几帧。
关于java - 多个递归方法调用的各种堆栈大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20937907/