什么是StackOverflowError?

什么是StackOverflowError ,是什么原因造成的,我该如何处理它们?

参数和局部variables分配在堆栈上(对象在堆上引用types,variables引用该对象)。 堆栈通常位于地址空间的上端,并在用完之后朝向地址空间的底部(即趋近于零)。

你的过程也有一堆,生活在你的过程的底端。 当你分配内存的时候,这个堆可以增长到你的地址空间的高端。 正如你所看到的,这个堆有可能与堆栈“碰撞”(有点像构造板!!!)。

堆栈溢出的常见原因是recursion调用不好。 通常,这是由于recursion函数没有正确的终止条件导致的,所以最终会永远调用它自己。 但是,使用GUI编程,可以生成间接recursion。 例如,您的应用程序可能正在处理绘画消息,并且在处理绘画消息时,可能会调用一个使系统发送另一个绘画消息的函数。 在这里你没有明确地称呼你自己,但是OS / VM已经为你做了。

要处理它们,你需要检查你的代码。 如果你有自己调用的函数,那么检查你是否有终止条件。 如果你已经检查了,而不是调用函数的时候,你至less要修改其中一个参数,否则recursion调用的函数将没有可见的变化,并且终止条件是无用的。

如果你没有明确的recursion函数,那么检查一下你是否调用了间接会导致你的函数被调用的库函数(就像上面的隐式情况)。

如果你有这样的function:

 int foo() { // more stuff foo(); } 

然后,foo()将继续调用自己,越来越深,当用于跟踪你所在function的空间被填满时,你会得到堆栈溢出错误。

为了描述这一点,首先让我们了解局部variables和对象是如何存储的,局部variables存储在堆栈中 在这里输入图像描述

如果你看图像,你应该能够理解事情是如何工作的。

当Java应用程序调用函数调用时,会在调用堆栈上分配一个堆栈帧。 堆栈帧包含被调用方法的参数,其本地参数以及方法的返回地址。 返回地址表示执行点,在调用的方法返回之后,程序将继续执行。 如果没有空间用于新的堆栈框架,那么StackOverflowError由Java虚拟机(JVM)抛出。 可能会耗尽Java应用程序堆栈的最常见情况是recursion。 在recursion中,一个方法在执行期间调用自己。 recursion被认为是一个强大的通用编程技术,但必须谨慎使用,以避免StackOverflowError。 下面显示了一个引发StackOverflowError的例子:

StackOverflowErrorExample.java:

 public class StackOverflowErrorExample { public static void recursivePrint(int num) { System.out.println("Number: " + num); if(num == 0) return; else recursivePrint(++num); } public static void main(String[] args) { StackOverflowErrorExample.recursivePrint(1); } } 

在这个例子中,我们定义了一个recursion方法,称为recursivePrint,它打印一个整数,然后自己调用,下一个连续的整数作为参数。 一旦我们调用方法,recursion结束,传递0作为参数。 然而,在我们的例子中,我们从1开始打印数字,因此,recursion永远不会终止。 下面显示了使用指定线程堆栈大小等于1MB的-Xss1M标志执行的示例:

 Number: 1 Number: 2 Number: 3 ... Number: 6262 Number: 6263 Number: 6264 Number: 6265 Number: 6266 Exception in thread "main" java.lang.StackOverflowError at java.io.PrintStream.write(PrintStream.java:480) at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221) at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291) at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104) at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185) at java.io.PrintStream.write(PrintStream.java:527) at java.io.PrintStream.print(PrintStream.java:669) at java.io.PrintStream.println(PrintStream.java:806) at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:4) at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9) at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9) at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9) ... 

根据JVM的初始configuration,结果可能会有所不同,但最终会抛出StackOverflowError。 这个例子是一个非常好的例子,说明recursion如何不引起问题,如果不谨慎实施的话。

如何处理StackOverflowError

  1. 最简单的解决scheme是仔细检查堆栈跟踪并检测行号的重复模式。 这些行号表示recursion调用的代码。 一旦你检测到这些行,你必须仔细检查你的代码,并理解为什么recursion永远不会终止。

  2. 如果您已经validation了recursion是正确实现的,则可以增加堆栈的大小,以便允许大量的调用。 根据安装的Java虚拟机(JVM),默认的线程堆栈大小可能等于512KB或1MB。 您可以使用-Xss标志增加线程堆栈大小。 这个标志可以通过项目的configuration或通过命令行来指定。 -Xss参数的格式是:-Xss [g | G | m | M | k | K]

堆栈溢出意味着:堆栈溢出。 通常程序中有一个堆栈,它包含本地作用域variables以及程序执行结束时要返回的地址。 这个堆栈往往是内存中某个固定的内存区域,因此限制它可以包含多less值。

如果堆栈是空的,你不能popup,如果你这样做,你会得到堆栈下溢错误。

如果堆栈满了,你不能推动,如果你这样做,你会得到堆栈溢出错误。

所以堆栈溢出出现在你分配太多堆栈的地方。 例如,在所提到的recursion中。

一些实现优化了某些forms的recursion。 尾recursion尤其如此。 尾recursion例程是例程的forms,其中recursion调用作为例程所做的最后一件事情出现。 这样的常规呼叫简单地减less到跳跃。

有些实现只是为了recursion实现自己的堆栈,因此它们允许recursion继续,直到系统内存不足。

如果可以,最简单的办法就是增加堆栈大小。 如果你不能这样做,第二件好事就是看是否有明显的堆栈溢出。 尝试通过打电话前后的例程。 这可以帮助您找出失败的例程。

通常通过嵌套函数调用来调用堆栈溢出(在使用recursion(即,调用自身的函数时尤其容易)或在堆栈上分配大量内存的情况下使用堆会更加合适。

就像你说的,你需要显示一些代码。 🙂

当你的函数调用太深时,通常会发生堆栈溢出错误。 请参阅堆栈溢出代码高尔夫线程,了解如何发生这种情况的一些例子(尽pipe在这个问题的情况下,答案有意造成堆栈溢出)。

堆栈溢出的最常见原因是过深或无限recursion 。 如果这是你的问题, 这个关于Javarecursion的教程可以帮助你理解这个问题。

以下将给StackOverflowError:

 class StackOverflowDemo { public static void badRecursiveCall() { badRecursiveCall(); } public static void main(String[] args) { badRecursiveCall(); } } 

下面是一个逆转单个链表的recursionalgorithm的例子。 在具有以下规格(4G内存,英特尔酷睿i5 2.3GHz CPU,64位Windows 7)的笔记本电脑上,该函数将针对大小接近10,000的链接列表运行到StackOverflow错误。

我的观点是,我们应该明智地使用recursion,时刻考虑到系统的规模。 通常recursion可以转换为迭代程序,这个程序可以更好地扩展。 (在页面底部给出了一个相同algorithm的迭代版本,它在9毫秒内反转了一个大小为100万的单链表。)

  private static LinkedListNode doReverseRecursively(LinkedListNode x, LinkedListNode first){ LinkedListNode second = first.next; first.next = x; if(second != null){ return doReverseRecursively(first, second); }else{ return first; } } public static LinkedListNode reverseRecursively(LinkedListNode head){ return doReverseRecursively(null, head); } 

相同algorithm的迭代版本:

  public static LinkedListNode reverseIteratively(LinkedListNode head){ return doReverseIteratively(null, head); } private static LinkedListNode doReverseIteratively(LinkedListNode x, LinkedListNode first) { while (first != null) { LinkedListNode second = first.next; first.next = x; x = first; if (second == null) { break; } else { first = second; } } return first; } public static LinkedListNode reverseIteratively(LinkedListNode head){ return doReverseIteratively(null, head); } 

术语“溢出(溢出)”通常被使用,但是用词不当; 攻击不会溢出堆栈,而是堆栈中的缓冲区。

Java中的运行时错误中的StackOverflowError。 当应用程序recursion得太深时抛出它。 只要方法执行发生,就会分配一个内存位置有限的调用栈。 这个内存量由JVM分配。 根据执行方法的数量,调用堆栈的大小会增大并缩小。

在大多数情况下,当StackOverError由于过度深度或无限recursion而导致调用堆栈超出时抛出。 在方法中需要存储局部variables的Durning超出了分配的堆栈大小。 在这样一个StackOverFlowError发生。 它显示在下面的程序中:

 public class Factorial { public static int factorial(int n){ if(n == 1){ return 1; } else{ return n * factorial(n-1); } } public static void main(String[] args){ System.out.println("Main method started"); int result = Factorial.factorial(-1); System.out.println("Factorial ==>"+result); System.out.println("Main method ended"); } } 

这里我们使用java中的recursion实现了阶乘。 当正数被作为“factorial”方法的parameter passing时,代码工作正常。 但是当我们提供负数作为参数时会出现问题。 在这种情况下,recursion将永远不会进入导致堆栈溢出的退出条件,并且会抛出此exception。

 Main method started Exception in thread "main" java.lang.StackOverflowError at com.program.stackoverflow.Factorial.factorial(Factorial.java:9) at com.program.stackoverflow.Factorial.factorial(Factorial.java:9) at com.program.stackoverflow.Factorial.factorial(Factorial.java:9) 

在上述情况下,可以避免进行程序化更改。 但是如果程序逻辑正确,仍然会发生堆栈大小需要增加

您可以使用-Xss4m来增加堆栈大小。

这是一个例子

 public static void main(String[] args) { System.out.println(add5(1)); } public static int add5(int a) { return add5(a) + 5; } 

一个StackOverflowError基本上是当你试图做一些事情,最有可能调用自己,并继续无穷(或直到它给一个StackOverflowError)。

add5(a)将自己调用,然后再次调用自己,依此类推。