什么是StackOverflowError,是什么原因导致的,我应该如何处理它们?
当前回答
要描述这一点,首先让我们了解局部变量和对象是如何存储的。
局部变量存储在堆栈上:
如果你看了图片,你应该能够理解事情是如何工作的。
当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]
其他回答
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)
在上述情况下,可以通过进行编程更改来避免这种情况。 但如果程序逻辑是正确的,它仍然发生,那么你的堆栈大小需要增加。
这个问题的很多答案都是好的。但是,我想采取稍微不同的方法,并对内存的工作原理和(简化的)可视化更好地理解StackOverflow错误给出一些更深入的见解。这种理解不仅适用于Java,而且适用于所有进程。
在现代系统中,所有新进程都有自己的虚拟地址空间(VAS)。从本质上讲,VAS是操作系统在物理内存之上提供的抽象层,目的是确保进程之间不相互干扰内存。内核的任务是将虚拟地址映射到实际的物理地址。
VAS可以分为几个部分:
为了让CPU知道它应该做什么,机器指令必须加载到内存中。这通常被称为代码或文本段,具有静态大小。
在此之上,可以找到数据段和堆。数据段大小固定,包含全局变量或静态变量。 当程序遇到特殊情况时,它可能需要额外分配数据,这就是堆发挥作用的地方,因此它能够动态地增长大小。
堆栈位于虚拟地址空间的另一侧,并使用后进先出(LIFO)数据结构跟踪所有函数调用。与堆类似,程序在运行时可能需要额外的空间来跟踪正在调用的新函数调用。由于堆栈位于VAS的另一侧,它正向相反的方向增长,即朝着堆的方向增长。
博士TL;
这就是StackOverflow错误发挥作用的地方。
由于堆栈向下增长(朝向堆),可能会发生在某个时间点它不能继续增长,因为它会与堆地址空间重叠。一旦发生这种情况,就会发生StackOverflow错误。
发生这种情况最常见的原因是由于程序中的一个错误导致递归调用不能正确终止。
请注意,在某些系统上,VAS的行为可能略有不同,甚至可以分为更多的部分,但是,这种一般理解适用于所有UNIX系统。
下面是一个递归算法的例子,用于反转单链表。在笔记本电脑(规格为4gb内存,Intel Core i5 2.3 GHz CPU 64位,Windows 7)上,对于大小接近10,000的链表,该函数将遇到StackOverflow错误。
我的观点是,我们应该明智地使用递归,始终考虑到系统的规模。
通常递归可以转换为迭代程序,迭代程序的伸缩性更好。(本页底部给出了同一算法的一个迭代版本。它在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);
}
同一算法的迭代版本:
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);
}
在紧急情况下,以下情况将导致堆栈溢出错误。
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字符串—查看字符串是否只包含数字而不包含字母
- Mockito.any()传递带有泛型的接口
- 在IntelliJ 10.5中运行测试时,出现“NoSuchMethodError: org.hamcrest. matcher . descripbemismatch”
- 使用String.split()和多个分隔符
- django MultiValueDictKeyError错误,我如何处理它
- Java数组有最大大小吗?
- 在Android中将字符串转换为Uri
- 从JSON生成Java类?
- 为什么java.util.Set没有get(int index)?
- Swing和AWT的区别是什么?
- 为什么Java流是一次性的?
- 四舍五入BigDecimal *总是*有两位小数点后
- 设计模式:工厂vs工厂方法vs抽象工厂
- 为什么这个迭代的列表增长代码给出IndexError:列表赋值索引超出范围?如何重复添加(追加)元素到列表?