什么是StackOverflowError
,是什么导致它,我应该如何处理它们?
答案 0 :(得分:362)
参数和局部变量在堆栈上分配(带引用类型,对象存在于堆上,堆栈中的变量引用堆上的对象) 。堆栈通常位于地址空间的上端,当它用完时,它朝向地址空间的底部(即朝向零)。
您的流程还有堆,它位于流程的底部端。在分配内存时,此堆可以向地址空间的上端增长。正如你所看到的那样,堆有可能与堆栈“碰撞”(有点像构造板块!!!)。
堆栈溢出的常见原因是错误的递归调用。通常,这是在递归函数没有正确的终止条件时引起的,因此最终会自动调用它。
但是,使用GUI编程,可以生成间接递归。例如,您的应用程序可能正在处理绘制消息,并且在处理它们时,它可能会调用一个函数,使系统发送另一个绘制消息。在这里你没有明确地称呼自己,但OS / VM已经为你完成了。
要处理它们,您需要检查代码。如果你有自己调用的函数,那么检查你是否有终止条件。如果你有,那么检查一下,在调用函数时你至少修改了一个参数,否则递归调用的函数没有明显的变化,终止条件也没用。
如果你没有明显的递归函数,那么检查你是否正在调用间接将导致你的函数被调用的任何库函数(如上面的隐含情况)。
答案 1 :(得分:86)
为了描述这一点,首先让我们了解本地变量和对象是如何存储的。
本地变量存储在堆栈中:
如果您查看图像,您应该能够了解事情是如何运作的。
当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
最简单的解决方案是仔细检查堆栈跟踪和 检测行号的重复模式。这些行号 表示递归调用的代码。一旦你发现了这些 行,你必须仔细检查你的代码,并了解为什么 递归永远不会终止。
如果您已经验证了递归
如果实现正确,你可以增加堆栈的大小
为了允许更多的调用。取决于Java
安装虚拟机(JVM),默认线程堆栈大小可以
等于 512KB或1MB 。您可以增加线程堆栈
使用-Xss
标志的大小。可以通过指定此标志
项目的配置,或通过命令行。格式
-Xss
参数是:
-Xss<size>[g|G|m|M|k|K]
答案 2 :(得分:62)
如果您有以下功能:
int foo()
{
// more stuff
foo();
}
然后foo()将继续调用自身,越来越深入,当用于跟踪你所处的函数的空间被填满时,你会得到堆栈溢出错误。
答案 3 :(得分:23)
堆栈溢出意味着:堆栈溢出。通常程序中有一个包含局部范围变量的堆栈,以及在执行例程结束时返回的地址。该堆栈往往是内存中某个固定的内存范围,因此它限制了它可以包含的值。
如果堆栈为空,则无法弹出,如果这样,您将收到堆栈下溢错误。
如果堆栈已满,则无法推送,如果这样,您将收到堆栈溢出错误。
因此,堆栈溢出会出现在堆栈中分配太多的地方。例如,在上面提到的递归中。
某些实现优化了某些形式的递归。特别是尾递归。尾递归例程是例程的形式,其中递归调用作为例程的最终事件出现。这样的例行调用只会简化为跳跃。
有些实现甚至可以实现自己的递归堆栈,因此它们允许递归继续,直到系统内存不足。
如果可以,您可以尝试的最简单的方法是增加堆栈大小。如果你不能这样做,那么第二个最好的事情是看是否有明显导致堆栈溢出的东西。通过在调用例程之前和之后打印一些东西来尝试它。这有助于您找出失败的例程。
答案 4 :(得分:8)
堆栈溢出通常通过嵌套函数调用过深来调用(特别是在使用递归时很容易,即调用自身的函数)或在堆栈上分配大量内存时使用堆更合适。
答案 5 :(得分:6)
就像你说的,你需要显示一些代码。 : - )
当函数调用嵌套太深时,通常会发生堆栈溢出错误。有关如何发生这种情况的一些示例,请参阅Stack Overflow Code Golf主题(尽管在该问题的情况下,答案会故意导致堆栈溢出)。
答案 6 :(得分:5)
堆栈溢出的最常见原因是过深或无限递归。如果这是您的问题,this tutorial about Java Recursion可以帮助您解决问题。
答案 7 :(得分:5)
StackOverflowError
是堆栈,因为OutOfMemoryError
是堆。
无限递归调用导致堆栈空间用完。
以下示例生成StackOverflowError
:
class StackOverflowDemo
{
public static void unboundedRecursiveCall() {
unboundedRecursiveCall();
}
public static void main(String[] args)
{
unboundedRecursiveCall();
}
}
如果限制递归调用以防止不完整的内存中调用(以字节为单位)的总数超过堆栈大小(以字节为单位),则 StackOverflowError
是可以避免的。
答案 8 :(得分:3)
以下是用于反转单链表的递归算法的示例。在具有以下规格的笔记本电脑(4G内存,英特尔酷睿i5 2.3GHz 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);
}
答案 9 :(得分:3)
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)
在上述情况下,可以避免进行程序化更改。 但是如果程序逻辑正确但仍然发生,则需要增加堆栈大小。
答案 10 :(得分:3)
堆栈的空间限制取决于操作系统,正常大小为8MB(在Ubuntu中,您可以使用$ ulimit -u
检查该限制,并且可以类似地在其他操作系统中检查)。任何程序都会在运行时使用堆栈,但是要完全知道何时使用它,您需要检查一种汇编语言。例如,在x86_64中,堆栈用于:
如果您不了解x86_64(正常情况),则只需知道您正在使用的特定高水平编程语言何时编译为这些操作即可。例如在C:
因此,在C语言中,仅局部变量和函数调用会使用堆栈。导致堆栈溢出的2种(唯一方法)是:
int array[10000][10000];
中声明太大的局部变量要避免使用StackOverflowError
,您可以:
检查局部变量是否太大(顺序为1 MB)→使用堆(malloc / calloc调用)或全局变量。
检查无限递归→您知道该怎么做...对其进行纠正!
检查正常的过深递归→最简单的方法是将实现更改为迭代式。
还请注意,全局变量,包括库等...不要利用堆栈。
仅当以上操作无效时,将堆栈大小更改为特定OS上的最大值。以Ubuntu为例:$ ulimit -s 32768
(32 MB)。 (这从来没有解决我的任何堆栈溢出错误,但是我也没有太多经验)
我已经省略了C语言中的特殊情况和/或非标准情况(例如alloc()
等的用法),因为如果您使用它们,那么您应该已经完全知道自己在做什么。
干杯!
答案 11 :(得分:1)
在紧要关头,以下情况将带来堆栈溢出错误。
public class Example3 {
public static void main(String[] args) {
main(new String[1]);
}
}
答案 12 :(得分:1)
简单的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();
}
}
答案 13 :(得分:0)
经常使用术语“堆栈溢出(溢出)”,但用词不当;攻击不会溢出堆栈,而是缓冲堆栈。
的演讲幻灯片答案 14 :(得分:0)
这是一个例子
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)
会调用自己,然后再次调用自己,依此类推。
答案 15 :(得分:0)
这是java.lang.StackOverflowError
的典型案例...该方法以doubleValue()
,floatValue()
等方式递归调用自身而没有退出。
public class Rational extends Number implements Comparable<Rational> {
private int num;
private int denom;
public Rational(int num, int denom) {
this.num = num;
this.denom = denom;
}
public int compareTo(Rational r) {
if ((num / denom) - (r.num / r.denom) > 0) {
return +1;
} else if ((num / denom) - (r.num / r.denom) < 0) {
return -1;
}
return 0;
}
public Rational add(Rational r) {
return new Rational(num + r.num, denom + r.denom);
}
public Rational sub(Rational r) {
return new Rational(num - r.num, denom - r.denom);
}
public Rational mul(Rational r) {
return new Rational(num * r.num, denom * r.denom);
}
public Rational div(Rational r) {
return new Rational(num * r.denom, denom * r.num);
}
public int gcd(Rational r) {
int i = 1;
while (i != 0) {
i = denom % r.denom;
denom = r.denom;
r.denom = i;
}
return denom;
}
public String toString() {
String a = num + "/" + denom;
return a;
}
public double doubleValue() {
return (double) doubleValue();
}
public float floatValue() {
return (float) floatValue();
}
public int intValue() {
return (int) intValue();
}
public long longValue() {
return (long) longValue();
}
}
public class Main {
public static void main(String[] args) {
Rational a = new Rational(2, 4);
Rational b = new Rational(2, 6);
System.out.println(a + " + " + b + " = " + a.add(b));
System.out.println(a + " - " + b + " = " + a.sub(b));
System.out.println(a + " * " + b + " = " + a.mul(b));
System.out.println(a + " / " + b + " = " + a.div(b));
Rational[] arr = {new Rational(7, 1), new Rational(6, 1),
new Rational(5, 1), new Rational(4, 1),
new Rational(3, 1), new Rational(2, 1),
new Rational(1, 1), new Rational(1, 2),
new Rational(1, 3), new Rational(1, 4),
new Rational(1, 5), new Rational(1, 6),
new Rational(1, 7), new Rational(1, 8),
new Rational(1, 9), new Rational(0, 1)};
selectSort(arr);
for (int i = 0; i < arr.length - 1; ++i) {
if (arr[i].compareTo(arr[i + 1]) > 0) {
System.exit(1);
}
}
Number n = new Rational(3, 2);
System.out.println(n.doubleValue());
System.out.println(n.floatValue());
System.out.println(n.intValue());
System.out.println(n.longValue());
}
public static <T extends Comparable<? super T>> void selectSort(T[] array) {
T temp;
int mini;
for (int i = 0; i < array.length - 1; ++i) {
mini = i;
for (int j = i + 1; j < array.length; ++j) {
if (array[j].compareTo(array[mini]) < 0) {
mini = j;
}
}
if (i != mini) {
temp = array[i];
array[i] = array[mini];
array[mini] = temp;
}
}
}
}
2/4 + 2/6 = 4/10
Exception in thread "main" java.lang.StackOverflowError
2/4 - 2/6 = 0/-2
at com.xetrasu.Rational.doubleValue(Rational.java:64)
2/4 * 2/6 = 4/24
at com.xetrasu.Rational.doubleValue(Rational.java:64)
2/4 / 2/6 = 12/8
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)