Java 递归调用Main

Java 递归调用Main,java,recursion,Java,Recursion,在这个程序中,我使用实例变量调用main 它在某一点上正常运行,但在一些Hello打印后,它会出现StackOverFlow异常 所以我用int来计算它被打印了多少次 我运行这个程序,它在I=4158之后出现异常 但我运行了好几次,它在不同的值下给出了异常,比如415541244154等等 正如我所知,这里的StackOverFlow是由于错误的或无条件的递归调用而生成的 我想弄明白,但不知道到底发生了什么 我想知道为什么在4158(或其他值)之后? 它是依赖于我的系统还是依赖于我的程序?堆栈溢

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

它在某一点上正常运行,但在一些
Hello
打印后,它会出现
StackOverFlow
异常

所以我用int来计算它被打印了多少次

我运行这个程序,它在
I=4158
之后出现异常

但我运行了好几次,它在不同的值下给出了异常,比如415541244154等等

正如我所知,这里的
StackOverFlow
是由于错误的或无条件的递归调用而生成的

我想弄明白,但不知道到底发生了什么

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


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

堆栈溢出是一种常见的编程错误情况,发生这种情况的原因是您已达到不返回递归调用的次数限制。这不仅仅影响Java

每次调用函数时,都会创建一个“堆栈框架”,其中包含函数的执行上下文,例如其局部变量。但是,每个堆栈帧都会占用一定数量的内存。当为函数调用分配的可用内存不足,或者达到某个系统/环境强加的限制(例如运行时施加了10兆字节的限制,即使有1千兆字节的可用内存)时,就会发生堆栈溢出

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

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

至于为什么在上面的示例中,
i
的值不断变化,
i
基本上表示在可用内存耗尽之前,递归已经走了多远。我不太清楚Java虚拟机和运行时环境的详细信息,但我猜每次的值都略有不同,因为每次运行程序时,由于内存垃圾收集之类的原因,可用内存量略有不同。

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

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

例如,这就是我使用
-Xmx6G
运行时得到的结果:

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);
    }
}
由于我的操作系统运行的其他内容,数字可能有所不同

现在,由于其耗尽的原因:您的调用被放置在堆栈上,而堆栈在内存中不是有限的位置;它可能(有时确实)用完。

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

10791
10796
10789
始终调用
main()
,因此它始终位于堆栈的底部

10791
10796
10789
如果我们要再次调用
main()
,则会在堆栈上放置另一个调用:

First time through:
 > main(0)
对于大多数简单的应用程序,只有少数调用(100以下)被放入调用堆栈,并且它们的生命周期很短,在调用堆栈上不会持续很长时间

但是,您的应用程序不同,因为它缺少称为基本情况的东西。这就是您用来决定停止递归的方法

以著名的阶乘函数为例,它指出:

Second time through:
 > main(1)
 > main(0)
一旦到达基本情况,我就停止向堆栈中添加调用——实际上我开始解析它们

下面是
阶乘(4)
的示例:

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

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

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

我已经在64位jvm上使用-Xss128k-Xss256k-Xss512k值测试了您的示例

我得到了96924675436

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

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

您的进程还有一个堆,它位于进程的底部。当您分配内存时,这个堆可以向地址空间的上端增长。如您所见,堆可能与堆栈“碰撞”(有点像Technonic plates!!!)

堆栈溢出的常见原因是错误的递归调用。通常,当递归函数没有正确的终止条件时,会导致这种情况,因此它会永远调用自己。然而,通过gui编程,可以生成间接递归。例如,您的应用程序可能正在处理绘制消息,在处理这些消息时,它可能会调用导致系统发送另一条绘制消息的函数。这里您没有显式地调用自己,但OS/VM已经为您完成了调用

要处理它们,您需要检查代码。如果你有自己调用的函数,那么检查你是否有终止条件。
> 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