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
(或其他值)之后?
它是依赖于我的系统还是依赖于我的程序?
答案 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已经为你完成了。
要处理它们,您需要检查代码。如果你有自己调用的函数,那么检查你是否有终止条件。如果你已经检查过,那么在调用函数时你至少修改了一个参数,否则对于重新调用的函数没有明显的变化,终止条件也没用。
如果你没有明显的递归函数,那么检查你是否正在调用任何间接导致你的函数被调用的库函数(如上面的隐含情况)。