递归调用Main

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

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

它可以正常运行到某些点但是在一些Hello打印之后它会产生StackOverFlow Exception。

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

我运行这个程序,它在i=4158之后给出了Exception。

但我运行了几次,它给出了不同值的exception,如4155,4124,4154等。

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

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

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

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

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

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

作为一个例子,这是我用-Xmx6G运行时-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 

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

堆栈溢出是一种常见的编程错误情况,因为您已达到可以在不返回的情况下进行的递归调用次数的限制。 这不仅仅影响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虚拟机和运行时环境的细节知之甚少,但我猜每次都会略有不同,因为每次你可用的内存量略有不同运行程序,因为内存垃圾收集和类似的东西。

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

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

我得到了969,2467,5436。

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

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

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

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

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

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