Zig*_*ggy 420 stack-overflow memory-leaks exception-handling out-of-memory
什么是什么,是StackOverflowError
什么导致它,我应该如何处理它们?
Sea*_*ean 387
参数和局部变量在堆栈上分配(对象存在于堆上的引用类型和引用该对象的变量).堆栈通常位于地址空间的上端,当它用完时,它朝向地址空间的底部(即朝向零).
您的进程还有一个堆,它位于流程的底端.在分配内存时,此堆可以向地址空间的上端增长.正如您所看到的,堆有可能与堆栈"碰撞"(有点像构造板块!!!).
堆栈溢出的常见原因是错误的递归调用.通常,这是在递归函数没有正确的终止条件时引起的,因此最终会自动调用它自己.
但是,使用GUI编程,可以生成间接递归.例如,您的应用程序可能正在处理绘制消息,并且在处理它们时,它可能会调用一个函数,使系统发送另一个绘制消息.在这里你没有明确地称呼自己,但OS/VM已经为你完成了.
要处理它们,您需要检查代码.如果你有自己调用的函数,那么检查你是否有终止条件.如果你有,那么检查一下,在调用函数时你至少修改了一个参数,否则递归调用的函数没有明显的变化,终止条件也没用.
如果你没有明显的递归函数,那么检查你是否正在调用任何间接导致函数被调用的库函数(如上面的隐含情况).
Var*_*run 97
为了描述这一点,首先让我们了解局部变量和对象是如何存储的.
局部变量存储在堆栈中:
如果您查看图像,您应该能够理解事物是如何工作的.
当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);
}
}
Run Code Online (Sandbox Code Playgroud)
在这个例子中,我们定义了一个递归方法,调用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)
...
Run Code Online (Sandbox Code Playgroud)
根据JVM的初始配置,结果可能会有所不同,但最终StackOverflowError
会抛出.这个例子是递归如何导致问题的一个非常好的例子,如果没有谨慎实施的话.
如何处理StackOverflowError
最简单的解决方案是仔细检查堆栈跟踪并检测行号的重复模式.这些行号表示递归调用的代码.一旦检测到这些行,就必须仔细检查代码并理解递归永不终止的原因.
如果您已验证正确实现了递归,则可以增加堆栈的大小,以便允许更多的调用.根据安装的Java虚拟机(JVM),默认线程堆栈大小可能等于512KB或1MB.您可以使用-Xss
标志增加线程堆栈大小.可以通过项目的配置或命令行指定此标志.-Xss
参数的格式
是:
-Xss<size>[g|G|m|M|k|K]
Kho*_*oth 62
如果你有这样的功能:
int foo()
{
// more stuff
foo();
}
Run Code Online (Sandbox Code Playgroud)
然后foo()将继续调用自身,越来越深,当用于跟踪你所处的函数的空间被填满时,你会得到堆栈溢出错误.
Che*_*ery 24
堆栈溢出意味着:堆栈溢出.通常程序中有一个包含局部范围变量的堆栈,以及在执行例程结束时返回的地址.该堆栈往往是内存中某个固定的内存范围,因此它限制了它可以包含多少值.
如果堆栈为空,则无法弹出,如果这样,您将遇到堆栈下溢错误.
如果堆栈已满,则无法推送,如果这样,您将收到堆栈溢出错误.
因此,堆栈溢出会出现在堆栈中分配太多的地方.例如,在上面提到的递归中.
一些实现优化了某些形式的递归.特别是尾递归.尾递归例程是例程的形式,其中递归调用作为例程的最终事件出现.这种常规调用简单地简化为跳转.
有些实现甚至可以实现自己的递归堆栈,因此它们允许递归继续,直到系统内存不足.
如果可以的话,你可以尝试的最简单的事情是增加堆栈大小.如果你不能这样做,那么第二个最好的事情是看是否有明显导致堆栈溢出的东西.通过在调用例程之前和之后打印一些东西来尝试它.这有助于您找出失败的例程.
AStackOverflowError
是Java中的运行时错误。
当超出 JVM 分配的调用堆栈内存量时抛出该异常。
抛出异常的常见情况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");
}
}
Run Code Online (Sandbox Code Playgroud)
堆栈跟踪:
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");
}
}
Run Code Online (Sandbox Code Playgroud)
在上述情况下,可以通过进行程序更改来避免。但是,如果程序逻辑是正确的并且它仍然发生,那么您的堆栈大小需要增加。
就像你说的,你需要显示一些代码.:-)
当函数调用嵌套太深时,通常会发生堆栈溢出错误.有关如何发生这种情况的一些示例,请参阅Stack Overflow Code Golf线程(尽管在该问题的情况下,答案会故意导致堆栈溢出).
StackOverflowError
是堆栈OutOfMemoryError
的堆栈.
无限递归调用导致堆栈空间用完.
以下示例生成StackOverflowError
:
class StackOverflowDemo
{
public static void unboundedRecursiveCall() {
unboundedRecursiveCall();
}
public static void main(String[] args)
{
unboundedRecursiveCall();
}
}
Run Code Online (Sandbox Code Playgroud)
StackOverflowError
如果限制递归调用以防止不完整的内存中调用(以字节为单位)的总数超过堆栈大小(以字节为单位),则可以避免.
这是用于反转单链表的递归算法的示例。在笔记本电脑(规格为 4\xc2\xa0GB 内存、Intel Core\xc2\xa0i5 2.3\xc2\xa0GHz CPU 64 位和 Windows 7)上,对于大小接近 10,000 的链接列表,此函数将遇到 StackOverflow 错误。
\n我的观点是,我们应该明智地使用递归,始终考虑到系统的规模。
\n通常,递归可以转换为迭代程序,这样可以更好地扩展。(页面底部给出了同一算法的一个迭代版本。它在 9 毫秒内反转一个大小为 100 万的单链表。)
\nprivate static LinkedListNode doReverseRecursively(LinkedListNode x, LinkedListNode first){\n\n LinkedListNode second = first.next;\n\n first.next = x;\n\n if(second != null){\n return doReverseRecursively(first, second);\n }else{\n return first;\n }\n}\n\n\npublic static LinkedListNode reverseRecursively(LinkedListNode head){\n return doReverseRecursively(null, head);\n}\n
Run Code Online (Sandbox Code Playgroud)\n同一算法的迭代版本:
\npublic static LinkedListNode reverseIteratively(LinkedListNode head){\n return doReverseIteratively(null, head);\n}\n\n\nprivate static LinkedListNode doReverseIteratively(LinkedListNode x, LinkedListNode first) {\n\n while (first != null) {\n LinkedListNode second = first.next;\n first.next = x;\n x = first;\n\n if (second == null) {\n break;\n } else {\n first = second;\n }\n }\n return first;\n}\n\n\npublic static LinkedListNode reverseIteratively(LinkedListNode head){\n return doReverseIteratively(null, head);\n}\n
Run Code Online (Sandbox Code Playgroud)\n