什么是StackOverflowError?


439

什么是StackOverflowError,是什么原因引起的,我应该如何应对?


Java中的堆栈大小很小。有时,例如许多递归调用,您会遇到此问题。您可以循环设计代码。您可以在以下网址中找到通用的设计模式:jndanial.com/73
JNDanial

一种非显而易见的获取方式:将行添加new Object() {{getClass().newInstance();}};到某些静态上下文中(例如,main方法)。在实例上下文中无效(仅抛出InstantiationException)。
John McClane '18

Answers:


407

参数和局部变量在堆栈上分配(具有引用类型,对象位于堆上,而堆栈中的变量引用该对象在堆上)。堆栈通常居住在上部您的地址空间的端部,当它被用完它头朝向底部的地址空间(即,朝向零)。

您的流程还具有一个,该位于流程的最底端。分配内存时,该堆可能会朝地址空间的高端增长。如您所见,堆有可能与堆“碰撞”(有点像构造板块!!!)。

导致堆栈溢出的常见原因是错误的递归调用。通常,这是由于您的递归函数没有正确的终止条件而导致的,因此最终将永远调用自身。或者,当终止条件很好时,可能是由于在实现之前需要太多的递归调用引起的。

但是,使用GUI编程,可以生成间接递归。例如,您的应用程序可能正在处理绘画消息,并且在处理它们时,它可能会调用使系统发送另一个绘画消息的函数。在这里,您没有明确地称呼自己,但是OS / VM已经为您做到了。

要处理它们,您需要检查您的代码。如果您有调用自己的函数,请检查是否有终止条件。如果有的话,请检查在调用函数时是否至少修改了其中一个参数,否则递归调用的函数将没有可见的更改,并且终止条件无用。另外请注意,在达到有效的终止条件之前,堆栈空间可能会用完内存,因此请确保您的方法可以处理需要更多递归调用的输入值。

如果没有明显的递归函数,请检查是否正在调用任何间接导致您的函数被调用的库函数(如上述隐式情况)。


1
原始海报:嘿,太好了。那么递归总是引起堆栈溢出吗?还是其他事情也可以对他们负责?不幸的是,我使用的是图书馆...但是我不了解。
Ziggy

4
哈哈哈,所以这里是:while(points <100){addMouseListeners(); moveball(); checkforcollision(); 哇,我感到la脚,因为我没有意识到自己最终将获得一堆堆的鼠标侦听器……谢谢大家!
Ziggy

4
不,如果您在en.wikipedia.org/wiki/Stack_overflow上查找有关Wikipedia的文章,则堆栈溢出也可能是由于变量太大而无法在堆栈上分配的。
JB金王

8
应该指出的是,“处理”堆栈溢出错误几乎是不可能的。在大多数环境中,要处理该错误,需要在堆栈上运行代码,如果没有更多的堆栈空间,这将很困难。
热门点击2014年

3
@JB King:不适用于Java,因为Java仅将原始类型和引用保留在堆栈中。所有大的东西(数组和对象)都在堆上。
jcsahnwaldt说GoFundMonica 2015年

107

为了描述这一点,首先让我们了解如何存储局部变量和对象。

局部变量存储在堆栈中在此处输入图片说明

如果您查看图像,您应该能够理解事物的工作方式。

当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

  1. 最简单的解决方案是仔细检查堆栈跟踪并检测行号的重复模式。这些行号表示要递归调用的代码。一旦检测到这些行,就必须仔细检查代码并了解为什么递归永远不会终止。

  2. 如果您已验证递归已正确实现,则可以增加堆栈的大小,以允许进行更多的调用。根据安装的Java虚拟机(JVM),默认线程堆栈大小可能等于512KB或1MB。您可以使用该-Xss标志来增加线程堆栈的大小。可以通过项目的配置或通过命令行指定此标志。-Xss参数的格式 为: -Xss<size>[g|G|m|M|k|K]


使用Windows时,某些Java版本中似乎存在一个错误,其中-Xss参数仅在新线程上生效
goerlibe

65

如果您具有以下功能:

int foo()
{
    // more stuff
    foo();
}

然后,foo()会不断调用自身,并且越来越深,并且当用于跟踪您所使用的函数的空间被填满时,您会得到堆栈溢出错误。


12
错误。您的函数是尾递归的。大多数编译语言具有尾递归优化。这意味着递归减少到一个简单的循环中,并且在某些系统上,您将永远不会遇到这段代码的堆栈溢出。
爽快

不错,哪些非功能性语言支持尾递归?
horseyguy

@banister和javascript的一些实现
Pacerier,2012年

@horseyguy Scala支持Tail递归。
Ajit K'sagar '17

这抓住了可以造成堆栈溢出的本质。真好
像素

24

堆栈溢出恰好意味着:堆栈溢出。通常,程序中只有一个堆栈,其中包含局部作用域变量,并说明例程执行结束时返回的位置。该堆栈往往是内存中某个位置的固定内存范围,因此限制了它可以包含值的数量。

如果堆栈为空,则无法弹出,如果堆栈为空,则会出现堆栈下溢错误。

如果堆栈已满,则无法推动,如果这样做,则会出现堆栈溢出错误。

因此,在您向堆栈分配过多空间的地方会出现堆栈溢出。例如,在提到的递归中。

一些实现优化了某些形式的递归。尾递归尤其如此。尾递归例程是例程的形式,其中递归调用最终显示为例程所做的事情。这样的例行调用简单地简化为跳转。

某些实现甚至实现了自己的递归堆栈,因此它们允许递归继续进行,直到系统内存不足。

您可以尝试的最简单方法是增加堆栈大小。如果您不能执行此操作,那么第二件事就是查看是否有明显导致堆栈溢出的内容。通过在调用例程之前和之后打印一些内容来进行尝试。这可以帮助您找出失败的例程。


4
是否有堆栈下溢之类的问题
Pacerier,2012年

5
汇编中可能会发生堆栈下溢(弹出的次数超过您的压入次数),尽管在编译语言中这几乎是不可能的。我不确定,您可能能够找到C的alloca()的实现,该实现“支持”负数大小。
得分_

2
堆栈溢出恰好意味着:堆栈溢出。通常有在包含本地范围变量程序中的一个堆栈- >不行,每个线程都有自己的堆栈,它包含了含有局部变量的每个方法调用堆栈帧..
科瑞图加伊

9

堆栈溢出通常由嵌套函数调用太深(特别是在使用递归时,即调用自身的函数)很容易被调用,或者在堆栈上分配大量内存(使用堆更合适)时调用。


1
哎呀,没有看到Java标记
格雷格-

另外,从原始海报这里看:嵌套功能在哪些方面太深了?其他功能?并且:如何将内存分配给堆栈或堆(因为您知道,我显然不知道做这些事情之一)。
Ziggy

@Ziggy:是的,如果一个函数调用了另一个函数,又调用了另一个函数,依此类推,经过多个级别,您的程序将出现堆栈溢出。[续]
克里斯·杰斯特·杨

[...续]在Java中,您不能直接从堆栈中分配内存(而在C中,您可以,这是需要注意的事情),因此这不太可能成为原因。在Java中,所有直接分配都通过使用“ new”来自堆。
克里斯·杰斯特·杨

@ ChrisJester-Young不是真的,如果我的方法中有100个局部变量,那么所有这些变量都会毫无例外地进入堆栈中吗?
Pacerier

7

就像您说的那样,您需要显示一些代码。:-)

当函数调用嵌套太深时,通常会发生堆栈溢出错误。有关如何发生这种情况的一些示例,请参见堆栈溢出代码高尔夫球线程(尽管在该问题的情况下,答案有意导致堆栈溢出)。


1
我完全想添加代码,但是由于我不知道是什么原因导致堆栈溢出,所以我不确定要添加什么代码。添加所有代码会很me脚,不是吗?
Ziggy

您的项目是开源的吗?如果是这样,只需注册一个Sourceforge或github帐户,然后将所有代码上传到那里即可。:-)
克里斯·杰斯特·杨

这听起来像是个好主意,但我实在是个菜鸟,我什至都不知道自己该上传什么。就像,我要导入的类(正在扩展的库)...对我来说都是未知的。噢,天哪!
Ziggy


5

StackOverflowError与堆栈一样OutOfMemoryError

无限制的递归调用会导致堆栈空间用尽。

以下示例产生StackOverflowError

class  StackOverflowDemo
{
    public static void unboundedRecursiveCall() {
     unboundedRecursiveCall();
    }

    public static void main(String[] args) 
    {
        unboundedRecursiveCall();
    }
}

StackOverflowError 如果绑定了递归调用以防止不完整的内存中调用(以字节为单位)的总和超过堆栈大小(以字节为单位),则可以避免。


3

这是一个用于逆转单链表的递归算法的示例。在具有以下规格(4G内存,Intel Core 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);
}

我认为使用JVM,笔记本电脑的规格实际上并不重要。
凯文

3

A 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)

在上述情况下,可以通过进行程序更改来避免这种情况。但是,如果程序逻辑正确并且仍然存在,则需要增加堆栈大小。


0

这是一个典型的案例java.lang.StackOverflowError...该方法是递归没有退出的自称doubleValue()floatValue()等等。

Rational.java

    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();
        }
    }

Main.java

    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)

这是StackOverflowErrorOpenJDK 7中的源代码


0

在紧急情况下,“低于”情况将带来堆栈溢出错误。

public class Example3 {

public static void main(String[] args) {

    main(new String[1]);

}

}


-1

这是一个例子

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) 会自称,然后再次自称,依此类推。


-1

经常使用术语“堆栈溢出(溢出)”,但用词不当;攻击不会使堆栈溢出,而是会在堆栈上进行缓冲。

-来自Dieter Gollmann教授的演讲幻灯片

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.