多个递归方法调用的各种堆栈大小

时间:2014-01-05 19:14:51

标签: java eclipse recursion jvm stack-overflow

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

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());打印的内容:

  • 第一次运行:4372
  • 第二轮:8364
  • 第3次运行:3381
  • 第4次运行:8406
  • 第五次运行:3485
你能看到吗?不断运行这个应用程序,我变量总是比上一次运行大~4500。

如果我们增加堆栈大小怎么办?将-Xss1m参数添加到VM args时,结果如下:

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

这是一回事!我的价值仍比上次运行的价格高出4500左右!

然而,当我们改变主要方法时:

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()永远不会被调用,因为错误会在它之前抛出。当foo.dooFoo()foo.reset()交换时,我们现在有了不变的结果:

  • 38394
  • 42394
  • 42394
  • 42394
  • 42394
  • 42394
  • 42394
  • 42394
  • 42394
  • 42394

但是,质疑为什么运行程序运行时有~4500次振荡,仍然是开放的。

== EDIT2

此问题仅与直接从Eclipse运行,不带-Xint参数有关。当程序从命令行java -cp . App启动时,i更加恒定:+/- 5。

1 个答案:

答案 0 :(得分:1)

我怀疑正在发生的事情(以及您的证据显示的是)方法被频繁调用,以至于您的代码最终会通过JIT编译器运行。当发生这种情况时,我推测优化可能会启动,并认识到每次执行i时递增doFoo都不是必需的 - 有点像展开循环。

我不完全确定为什么你会看到任何可变性,除了JIT启动时可能存在非确定性成本组件,或者偶尔它会在几帧内启动。