递归调用Main

时间:2014-03-25 03:35:19

标签: java recursion

public class Demo
{
    static int i=0;
    public static void main(String args[])
    {
        System.out.println("Hello"+(i++));
        main(args);
    }
}

在这个程序中,我用实例变量调用main。

它在某些时候正常运行,但在打印了Hello之后,它会给出StackOverFlow例外。

所以我把int放到它打印的次数。

我运行此程序,它在i=4158之后提供了Exception。

但是我运行了几次,它给出了不同值的异常,我喜欢 4155,4124,4154 等。

据我所知,StackOverFlow是由于错误或无条件的递归调用而生成的。

我试图找出它,但不知道到底发生了什么。

我想知道为什么在4158(或其他值)之后?

它是依赖于我的系统还是依赖于我的程序?

4 个答案:

答案 0 :(得分:3)

堆栈溢出是一种常见的编程错误情况,因为您已达到可以在不返回的情况下进行的递归调用次数的限制。这并不仅仅影响Java。

每次你调用一个函数,一个"堆栈框架"已创建,其中包含函数的执行上下文,例如其局部变量。但是,每个堆栈帧占用一定量的内存。如果您用完了为函数调用分配的可用内存,或者您已达到某个系统/环境强制限制(就像您的运行时强制限制为10兆字节,即使您有一千兆字节的内存可用。)

为了避免这种无限递归条件,你需要有一个结束案例/条件,你的函数确定它应该结束递归。这是一个示例,其中结束条件是递归深度达到最大值10,此时函数停止调用自身并最终返回:

public class Demo
{
    String args[] = new String[10];
    static int i = 0;

    public static void main(String args[])
    {
        if (i >= 10) {
            return;
        }

        System.out.println("Hello" + i++);
        main(args);
    }
}

至于为什么i的值在上面的示例中不断变化,i基本上表示在用完可用内存之前你已经走了多远。我不确定有关Java虚拟机和运行时环境的详细信息,但我猜这个值每次都略有不同,因为你有可用的内存量每次运行程序时都会略有不同,这是因为内存垃圾收集等等。

答案 1 :(得分:3)

首先,您正在隐藏args变量。您的字段中定义的args不会被视为您尝试以args递归调用的main

其次,递归最终耗尽,但这取决于您为应用程序分配了多少内存,以及当时内存中还有什么内存。如果你给它提供了2GB(或更多)空间,那么递归仍然会耗尽 - 但可能会有更高的值。

作为一个例子,这是我用-Xmx6G运行时得到的结果:

10791
10796
10789

由于我的操作系统正在运行,其数量可能会有所不同。

现在,对于原因它用完了:你的调用放在一个堆栈上,这在内存中不是一个有限的位置;它可以(有时确实)耗尽。

每次用Java调用函数时,它都会进入堆栈。

First time through:
 > main(0)
始终会调用

main(),因此它始终位于堆栈的底部。

如果我们再次呼叫main(),那么另一个呼叫将被放置在堆栈中:

Second time through:
 > main(1)
 > main(0)

对于大多数简单的应用程序,只有少数几个调用(100以下)被置于调用堆栈中,并且它们的生命周期足够短,以至于它们不会在调用堆栈上持续很长时间。

但是,您的应用程序不同,因为它缺少称为基本情况的内容。这是您决定停止递归的原因。

举例说明着名的因子函数,其中指出:

      { 1          if n = 0
n! = <
      { n * (n-1)! if n > 0

我们有基本案例:如果n = 0,那么我们不会继续进一步递减。否则,我们继续坚持下去。

以下是代码中的内容:

public long factorial(int n) {
    return n == 0 ? 1L : n * factorial(n-1);
}

一旦我到达我的基础案例,我就停止向堆栈添加调用 - 我实际上开始解析它们。

以下是factorial(4)的样子:

> factorial(4)
  > factorial(3)
    > factorial(2)
      > factorial(1)
        > factorial(0)
        > 1
      > 1 * 1
    > 1 * 1 * 2
  > 1 * 1 * 2 * 3
> 1 * 1 * 2 * 3 * 4

所以,这就是说:如果你要做一个递归函数,确保递归可以结束。否则,你将一直遇到这个问题

答案 2 :(得分:1)

它取决于堆栈大小-Xss而不是Xmx。

我在我的64位jvm上用值-Xss128k -Xss256k -Xss512k测试了你的例子。

我得到了969,2467,5436。

因此我们可以看到向堆栈添加128k可以提供约1500个新呼叫,添加256k可以提供~3000个呼叫。 这意味着一次调用需要大约80个字节的堆栈内存。 所以其中8个是对arg的引用,其他的看起来像一些服务信息来控制流(try catch)或其他东西。

答案 3 :(得分:0)

参数和局部变量在堆栈上分配(对象存在于堆上的引用类型和引用该对象的变量)。堆栈通常位于地址空间的上端,当它用完时,它会朝向地址空间的底部(即朝向零)。

您的流程还有一个堆,它位于流程的底端。在分配内存时,此堆可以向地址空间的上端增长。正如你所看到的,堆有可能与堆栈“碰撞”(有点像技术板块!!!)。

堆栈溢出的常见原因是错误的递归调用。通常,这是在递归函数没有正确的终止条件时引起的,因此它最终会自动调用它。但是,使用gui编程,可以生成间接递归。例如,您的应用程序可能正在处理绘制消息,并且在处理它时可能会调用一个函数,该函数会导致系统发送另一个绘制消息。在这里你没有明确地称呼自己,但OS / VM已经为你完成了。

要处理它们,您需要检查代码。如果你有自己调用的函数,那么检查你是否有终止条件。如果你已经检查过,那么在调用函数时你至少修改了一个参数,否则对于重新调用的函数没有明显的变化,终止条件也没用。

如果你没有明显的递归函数,那么检查你是否正在调用任何间接导致你的函数被调用的库函数(如上面的隐含情况)。