多个递归方法调用的不同堆栈大小



在实验中,我发现了与堆栈溢出错误有关的非常有趣的问题。看看这个代码:

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的值是多少?我已经用默认的VM堆栈大小测试过几次了——这里是System.out.println(foo.getI());打印的:

  • 第一次运行:4372
  • 第二次运行:8364
  • 第3次运行:3381
  • 第4次运行:8406
  • 第5次运行:3485

你能看到吗?不断运行此应用程序,i变量将始终比上次运行大/小~4500。

如果我们要增加堆栈大小呢?当将-Xss1m参数添加到VM参数时,以下是结果:

  • 第一次运行:33364
  • 第二次运行:38404
  • 第3次运行:33787
  • 第4次运行:38434
  • 第5次运行:33805

都一样!i值仍然比上一次运行大/小约4500!

Hoever,当我们把主要方法改为时

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次左右的振荡呢?PS。我直接从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启动时可能有一个不确定的成本成分,或者有时它会启动几帧。

最新更新