什么是StackOverflowError,是什么原因导致的,我应该如何处理它们?
当前回答
StackOverflowError是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");
}
}
堆栈跟踪:
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)
在上述情况下,可以通过进行编程更改来避免这种情况。 但如果程序逻辑是正确的,它仍然发生,那么你的堆栈大小需要增加。
其他回答
一个简单的Java示例,由于错误的递归调用导致Java .lang. stackoverflowerror:
class Human {
Human(){
new Animal();
}
}
class Animal extends Human {
Animal(){
super();
}
}
public class Test01 {
public static void main(String[] args) {
new Animal();
}
}
在紧急情况下,以下情况将导致堆栈溢出错误。
public class Example3 {
public static void main(String[] args) {
main(new String[1]);
}
}
要描述这一点,首先让我们了解局部变量和对象是如何存储的。
局部变量存储在堆栈上:
如果你看了图片,你应该能够理解事情是如何工作的。
当Java应用程序调用函数时,将在调用堆栈上分配堆栈帧。堆栈帧包含被调用方法的参数、局部参数和方法的返回地址。返回地址表示执行点,在调用的方法返回后,程序将从该执行点继续执行。如果没有空间用于新的堆栈帧,那么Java虚拟机(JVM)将抛出StackOverflowError。
可能耗尽Java应用程序堆栈的最常见情况是递归。在递归中,方法在执行过程中调用自身。递归被认为是一种强大的通用编程技术,但必须谨慎使用,以避免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);
}
}
在本例中,我们定义了一个递归方法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
The simplest solution is to carefully inspect the stack trace and detect the repeating pattern of line numbers. These line numbers indicate the code being recursively called. Once you detect these lines, you must carefully inspect your code and understand why the recursion never terminates. If you have verified that the recursion is implemented correctly, you can increase the stack’s size, in order to allow a larger number of invocations. Depending on the Java Virtual Machine (JVM) installed, the default thread stack size may equal to either 512 KB, or 1 MB. You can increase the thread stack size using the -Xss flag. This flag can be specified either via the project’s configuration, or via the command line. The format of the -Xss argument is: -Xss<size>[g|G|m|M|k|K]
堆栈溢出最常见的原因是过于深入或无限递归。如果这是你的问题,这个关于Java递归的教程可以帮助你理解这个问题。
这个问题的很多答案都是好的。但是,我想采取稍微不同的方法,并对内存的工作原理和(简化的)可视化更好地理解StackOverflow错误给出一些更深入的见解。这种理解不仅适用于Java,而且适用于所有进程。
在现代系统中,所有新进程都有自己的虚拟地址空间(VAS)。从本质上讲,VAS是操作系统在物理内存之上提供的抽象层,目的是确保进程之间不相互干扰内存。内核的任务是将虚拟地址映射到实际的物理地址。
VAS可以分为几个部分:
为了让CPU知道它应该做什么,机器指令必须加载到内存中。这通常被称为代码或文本段,具有静态大小。
在此之上,可以找到数据段和堆。数据段大小固定,包含全局变量或静态变量。 当程序遇到特殊情况时,它可能需要额外分配数据,这就是堆发挥作用的地方,因此它能够动态地增长大小。
堆栈位于虚拟地址空间的另一侧,并使用后进先出(LIFO)数据结构跟踪所有函数调用。与堆类似,程序在运行时可能需要额外的空间来跟踪正在调用的新函数调用。由于堆栈位于VAS的另一侧,它正向相反的方向增长,即朝着堆的方向增长。
博士TL;
这就是StackOverflow错误发挥作用的地方。
由于堆栈向下增长(朝向堆),可能会发生在某个时间点它不能继续增长,因为它会与堆地址空间重叠。一旦发生这种情况,就会发生StackOverflow错误。
发生这种情况最常见的原因是由于程序中的一个错误导致递归调用不能正确终止。
请注意,在某些系统上,VAS的行为可能略有不同,甚至可以分为更多的部分,但是,这种一般理解适用于所有UNIX系统。
推荐文章
- 到底是什么导致了堆栈溢出错误?
- 为什么Android工作室说“等待调试器”如果我不调试?
- Java:路径vs文件
- ExecutorService,如何等待所有任务完成
- Maven依赖Servlet 3.0 API?
- 如何在IntelliJ IDEA中添加目录到应用程序运行概要文件中的类路径?
- getter和setter是糟糕的设计吗?相互矛盾的建议
- Android room persistent: AppDatabase_Impl不存在
- Java的String[]在Kotlin中等价于什么?
- Intellij IDEA上的System.out.println()快捷方式
- 使用Spring RestTemplate获取JSON对象列表
- Spring JPA选择特定的列
- Objective-C中的自动引用计数不能防止或减少什么样的泄漏?
- URLEncoder不能翻译空格字符
- Java中的super()