什么是什么,是StackOverflowError
什么导致它,我应该如何处理它们?
参数和局部变量在堆栈上分配(对象存在于堆上的引用类型和引用该对象的变量).堆栈通常位于地址空间的上端,当它用完时,它朝向地址空间的底部(即朝向零).
您的进程还有一个堆,它位于流程的底端.在分配内存时,此堆可以向地址空间的上端增长.正如您所看到的,堆有可能与堆栈"碰撞"(有点像构造板块!!!).
堆栈溢出的常见原因是错误的递归调用.通常,这是在递归函数没有正确的终止条件时引起的,因此最终会自动调用它自己.
但是,使用GUI编程,可以生成间接递归.例如,您的应用程序可能正在处理绘制消息,并且在处理它们时,它可能会调用一个函数,使系统发送另一个绘制消息.在这里你没有明确地称呼自己,但OS/VM已经为你完成了.
要处理它们,您需要检查代码.如果你有自己调用的函数,那么检查你是否有终止条件.如果你有,那么检查一下,在调用函数时你至少修改了一个参数,否则递归调用的函数没有明显的变化,终止条件也没用.
如果你没有明显的递归函数,那么检查你是否正在调用任何间接导致函数被调用的库函数(如上面的隐含情况).
为了描述这一点,首先让我们了解局部变量和对象是如何存储的.
局部变量存储在堆栈中:
如果您查看图像,您应该能够理解事物是如何工作的.
当Java应用程序调用函数调用时,会在调用堆栈上分配堆栈帧.堆栈帧包含调用方法的参数,其本地参数以及方法的返回地址.返回地址表示执行点,程序执行将在调用方法返回后继续执行.如果没有新堆栈帧的空间,StackOverflowError
则由Java虚拟机(JVM)抛出.
可能耗尽Java应用程序堆栈的最常见情况是递归.在递归中,方法在执行期间调用自身.递归被认为是一种强大的通用编程技术,但必须谨慎使用,以避免StackOverflowError
.
投掷a的示例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);
}
}
在这个例子中,我们定义了一个递归方法,调用recursivePrint
它打印一个整数,然后调用自身,下一个连续的整数作为参数.递归结束,直到我们0
作为参数传入.但是,在我们的示例中,我们从1传递参数及其增加的关注者,因此递归永远不会终止.
使用-Xss1M
指定线程堆栈大小等于1MB 的标志的示例执行如下所示:
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的初始配置,结果可能会有所不同,但最终StackOverflowError
会抛出.这个例子是递归如何导致问题的一个非常好的例子,如果没有谨慎实施的话.
如何处理StackOverflowError
最简单的解决方案是仔细检查堆栈跟踪并检测行号的重复模式.这些行号表示递归调用的代码.一旦检测到这些行,就必须仔细检查代码并理解递归永不终止的原因.
如果您已验证正确实现了递归,则可以增加堆栈的大小,以便允许更多的调用.根据安装的Java虚拟机(JVM),默认线程堆栈大小可能等于512KB或1MB.您可以使用-Xss
标志增加线程堆栈大小.可以通过项目的配置或命令行指定此标志.-Xss
参数的格式
是:
-Xss
如果你有这样的功能:
int foo() { // more stuff foo(); }
然后foo()将继续调用自身,越来越深,当用于跟踪你所处的函数的空间被填满时,你会得到堆栈溢出错误.
堆栈溢出意味着:堆栈溢出.通常程序中有一个包含局部范围变量的堆栈,以及在执行例程结束时返回的地址.该堆栈往往是内存中某个固定的内存范围,因此它限制了它可以包含多少值.
如果堆栈为空,则无法弹出,如果这样,您将遇到堆栈下溢错误.
如果堆栈已满,则无法推送,如果这样,您将收到堆栈溢出错误.
因此,堆栈溢出会出现在堆栈中分配太多的地方.例如,在上面提到的递归中.
一些实现优化了某些形式的递归.特别是尾递归.尾递归例程是例程的形式,其中递归调用作为例程的最终事件出现.这种常规调用简单地简化为跳转.
有些实现甚至可以实现自己的递归堆栈,因此它们允许递归继续,直到系统内存不足.
如果可以的话,你可以尝试的最简单的事情是增加堆栈大小.如果你不能这样做,那么第二个最好的事情是看是否有明显导致堆栈溢出的东西.通过在调用例程之前和之后打印一些东西来尝试它.这有助于您找出失败的例程.
堆栈溢出通常通过嵌套函数调用过深来调用(特别是在使用递归时很容易,即调用自身的函数)或在堆栈上分配大量内存,使用堆更合适.
就像你说的,你需要显示一些代码.:-)
当函数调用嵌套太深时,通常会发生堆栈溢出错误.有关如何发生这种情况的一些示例,请参阅Stack Overflow Code Golf线程(尽管在该问题的情况下,答案会故意导致堆栈溢出).
堆栈溢出的最常见原因是过深或无限递归.如果这是您的问题,那么关于Java Recursion的本教程可以帮助您理解该问题.
StackOverflowError
是堆栈OutOfMemoryError
的堆栈.
无限递归调用导致堆栈空间用完.
以下示例生成StackOverflowError
:
class StackOverflowDemo { public static void unboundedRecursiveCall() { unboundedRecursiveCall(); } public static void main(String[] args) { unboundedRecursiveCall(); } }
StackOverflowError
如果限制递归调用以防止不完整的内存中调用(以字节为单位)的总数超过堆栈大小(以字节为单位),则可以避免.