什么是 StackOverflowError?

What is a StackOverflowError?

提问人:Ziggy 提问时间:10/18/2008 最后编辑:chrylis -cautiouslyoptimistic-Ziggy 更新时间:3/31/2023 访问量:840050

问:

什么是a,是什么原因导致的,我应该如何处理它们?StackOverflowError

Java 异常 泄漏 内存不足, 堆栈溢出

评论

0赞 Danial Jalalnouri 9/19/2018
java 中的堆栈大小很小。而有些时候,比如很多递归调用,你都会遇到这个问题。您可以按循环重新设计代码。您可以在以下 url 中找到一般设计模式:jndanial.com/73
0赞 John McClane 12/4/2018
一种不明显的方法:将行添加到一些静态上下文中(例如 方法)。在实例上下文中不起作用(仅引发)。new Object() {{getClass().newInstance();}};mainInstantiationException

答:

7赞 C. K. Young 10/18/2008 #1

就像你说的,你需要展示一些代码。:-)

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

评论

1赞 Ziggy 10/18/2008
我完全想添加代码,但由于我不知道是什么原因导致堆栈溢出,我不确定要添加什么代码。 添加所有代码会很蹩脚,不是吗?
0赞 C. K. Young 10/18/2008
您的项目是开源的吗?如果是这样,只需创建一个 Sourceforge 或 github 帐户,然后在那里上传您的所有代码。:-)
0赞 Ziggy 10/18/2008
这听起来是个好主意,但我是个菜鸟,我什至不知道我必须上传什么。比如,我正在导入的库,我正在扩展的类等......对我来说都是未知数。哦,伙计:糟糕的时光。
11赞 Greg 10/18/2008 #2

堆栈溢出通常是通过嵌套函数调用太深(在使用递归时尤其容易,即调用自身的函数)或在堆栈上分配大量内存来调用,其中使用堆会更合适。

评论

1赞 Greg 10/18/2008
哎呀,没有看到 Java 标签
0赞 Ziggy 10/18/2008
另外,从这里的原始海报来看:嵌套功能太深了,什么?其他功能?而且:如何为堆栈或堆分配内存(因为,你知道,我显然在不知情的情况下做了这些事情之一)。
0赞 C. K. Young 10/18/2008
@Ziggy:是的,如果一个函数调用另一个函数,而另一个函数又调用另一个函数,依此类推,经过许多级别后,程序将出现堆栈溢出。[继续]
0赞 C. K. Young 10/18/2008
[...continue] 在 Java 中,你不能直接从堆栈中分配内存(而在 C 中,你可以,这将是需要注意的事情),所以这不太可能是原因。在 Java 中,所有直接分配都来自堆,通过使用“new”。
0赞 Pacerier 1/29/2012
@ChrisJester-Young:如果我在一个方法中有 100 个局部变量,那么所有变量都会毫无例外地出现在堆栈上,这难道不是真的吗?
70赞 Khoth 10/18/2008 #3

如果您有以下功能:

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

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

评论

14赞 Cheery 10/18/2008
错。你的函数是尾递归的。大多数编译语言都有尾递归优化。这意味着递归简化为一个简单的循环,在某些系统上,你永远不会遇到这段代码的堆栈溢出。
0赞 horseyguy 8/11/2009
Cheery,哪些非函数式语言支持尾递归?
0赞 Pacerier 1/29/2012
JavaScript 的@banister和一些实现
0赞 AKs 11/20/2017
@horseyguy Scala 支持 Tail 递归。
1赞 1/19/2018
这抓住了可能造成堆栈溢出的本质。好。
461赞 Sean 10/18/2008 #4

参数和局部变量在堆栈上分配(对于引用类型,对象位于堆上,栈中的变量引用上的对象)。堆栈通常位于地址空间的端,当它用完时,它会朝向地址空间的底部(即接近零)。

您的流程也有一个,它位于流程的端。在分配内存时,此堆可以向地址空间的上限增长。正如你所看到的,堆有可能与堆栈“碰撞”(有点像构造板块!!)。

堆栈溢出的常见原因是错误的递归调用。通常,这是由于递归函数没有正确的终止条件,因此它最终会永远调用自己。或者,当终止条件良好时,可能是由于在完成终止之前需要太多的递归调用而引起的。

但是,通过 GUI 编程,可以生成间接递归。例如,你的应用可能正在处理绘制消息,并且在处理这些消息时,它可能会调用一个函数,导致系统发送另一个绘制消息。在这里,你没有显式地称呼自己,但 OS/VM 已经为你完成了。

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

如果你没有明显的递归函数,那么请检查你是否调用了任何间接导致你的函数被调用的库函数(如上面的隐式情况)。

评论

3赞 Ziggy 10/18/2008
原海报:嘿,这太棒了。所以递归总是导致堆栈溢出的原因?或者其他事情也可以对它们负责吗?不幸的是,我正在使用图书馆...但不是我理解的。
5赞 Ziggy 10/18/2008
哈哈哈,原来是这样:while (points < 100) {addMouseListeners(); moveball(); checkforcollision(); pause(speed);}哇,我感到很蹩脚,因为我没有意识到我最终会得到一堆鼠标听众......谢谢大家!
9赞 JB King 11/5/2008
不,堆栈溢出也可能来自变量太大而无法在堆栈上分配,如果您在 en.wikipedia.org/wiki/Stack_overflow 上查找有关它的维基百科文章。
10赞 Hot Licks 1/25/2014
应该指出的是,“处理”堆栈溢出错误几乎是不可能的。在大多数环境中,要处理错误,需要在堆栈上运行代码,如果没有更多的堆栈空间,这很困难。
7赞 jcsahnwaldt Reinstate Monica 1/23/2015
@JB King:这并不真正适用于 Java,在 Java 中,堆栈上只保留了原始类型和引用。所有大的东西(数组和对象)都在堆上。
5赞 splattne 10/18/2008 #5

堆栈溢出的最常见原因是过深或无限递归。如果这是您的问题,那么这个关于 Java 递归的教程可以帮助您理解该问题。

26赞 Cheery 10/18/2008 #6

堆栈溢出的意思是:堆栈溢出。通常,程序中有一个堆栈,其中包含局部范围的变量,并解决了例程执行结束时返回的位置。该堆栈往往是内存中某个位置的固定内存范围,因此它包含的值数量受到限制。

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

如果堆栈已满,则无法推送,如果推送,则会出现堆栈溢出错误。

因此,堆栈溢出出现在您在堆栈中分配过多的位置。例如,在提到的递归中。

某些实现优化了某些形式的递归。特别是尾部递归。尾部递归例程是例程的形式,其中递归调用显示为例程的最终功能。这种例行电话被简单地简化为跳跃。

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

如果可以的话,您可以尝试的最简单的事情是增加堆栈大小。但是,如果您不能做到这一点,那么第二好的办法是查看是否有明显导致堆栈溢出的东西。通过在调用例程之前和之后打印一些东西来尝试。这有助于您找出失败的例程。

评论

5赞 Pacerier 1/29/2012
有没有堆栈下溢这样的东西?
6赞 Score_Under 2/9/2013
在汇编中,堆栈下溢是可能的(弹出的比你推的多),尽管在编译语言中这几乎是不可能的。我不确定,您也许可以找到 C 的 alloca() 的实现,它“支持”负大小。
2赞 Koray Tugay 5/3/2015
堆栈溢出的意思是:堆栈溢出。通常程序中有一个堆栈包含局部范围变量 -> 不,每个线程都有自己的堆栈,其中包含每个包含局部变量的方法调用的堆栈帧。
6赞 Vikram 7/18/2012 #7

StackOverflowError对堆栈来说就像对堆一样。OutOfMemoryError

无界递归调用会导致堆栈空间被用完。

以下示例生成:StackOverflowError

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

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

StackOverflowError如果对递归调用进行限制以防止未完成的内存中调用的聚合总数(以字节为单位)超过堆栈大小(以字节为单位),则可以避免。

5赞 Yiling 1/17/2013 #8

下面是用于反转单向链表的递归算法示例。在笔记本电脑(规格为 4 GB 内存、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);
}

评论

0赞 kevin 10/24/2013
我认为使用 JVM,您的笔记本电脑的规格实际上并不重要。
0赞 John S. 2/12/2016 #9

下面是一个示例

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

7赞 Rahul Sah 3/30/2017 #10

A 是 Java 中的运行时错误。StackOverflowError

当超出 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赞 user8389458 4/2/2018 #11

这是一个典型的案例......该方法是递归调用自身的,没有退出 、 等。java.lang.StackOverflowErrordoubleValue()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)

以下是 OpenJDK 7 中 StackOverflowError 的源代码

2赞 Kaliappan 2/26/2020 #12

在紧缩中,以下情况将带来堆栈溢出错误。

public class Example3 {

    public static void main(String[] args) {

        main(new String[1]);
    }

}
3赞 MrIo 7/9/2020 #13

堆栈的空间限制取决于操作系统。正常大小为 8 MB(在 Ubuntu (Linux) 中,您可以检查该限制,也可以在其他操作系统中进行类似的检查)。任何程序在运行时都会使用堆栈,但要完全知道何时使用它,您需要检查汇编语言。例如,在x86_64中,堆栈用于:$ ulimit -u

  1. 在进行过程调用时保存返回地址
  2. 保存局部变量
  3. 保存特殊寄存器以备日后恢复
  4. 将参数传递给过程调用(当有超过 6 个整数参数和/或超过 8 个浮点参数时)
  5. 其他:随机未使用的堆栈基数、金丝雀值、填充等等。

如果你不知道x86_64(正常情况),你只需要知道你使用的特定高级编程语言何时编译到这些操作。例如,在 C 语言中:

  • (1) →函数调用
  • (2)函数调用中的局部变量(包括main)→
  • (3)函数调用中→局部变量(非主变量)
  • (4) →函数调用
  • (5) →通常为函数调用,但通常与堆栈溢出无关。

因此,在 C 语言中,只有局部变量和函数调用使用堆栈。使堆栈溢出的两种(独特?)方法是:

  • 在 main 或调用它的任何函数中声明太大的局部变量 (int array[10000][10000];)
  • 非常深或无限的递归(同时调用太多函数)。

为了避免StackOverflowError

  • 检查局部变量是否太大(大约 1 MB),→使用堆(malloc/calloc 调用)或全局变量。

  • 检查无限递归 →你知道该怎么做...纠正它!

  • 检查正常的太深递归 → 最简单的方法是将实现更改为迭代。

另请注意,全局变量,包括库等...不要使用堆栈。

仅当上述方法不起作用时,才将堆栈大小更改为特定操作系统上的最大值。以 Ubuntu 为例:(32 MB)。(这从来都不是我任何堆栈溢出错误的解决方案,但我也没有太多经验。ulimit -s 32768

我在 C 中省略了特殊和/或非标准情况(例如用法和类似情况),因为如果您正在使用它们,您应该已经确切地知道自己在做什么。alloc()

1赞 Yuresh Karunanayake 1/31/2021 #14

一个简单的 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();
    }
}
1赞 F1ko 12/15/2022 #15

这个问题的许多答案都很好。但是,我想采取稍微不同的方法,更深入地了解内存的工作原理以及(简化的)可视化,以更好地理解 StackOverflow 错误。这种理解不仅适用于 Java,也适用于所有进程。

在现代系统上,所有新进程都有自己的虚拟地址空间 (VAS)。从本质上讲,VAS是操作系统在物理内存之上提供的抽象层,以确保进程不会相互干扰内存。然后,内核的工作是将提供的虚拟地址映射到实际的物理地址。


VAS可以分为几个部分:

为了让 CPU 知道它应该做什么,必须将机器指令加载到内存中。这通常称为代码或文本段,具有静态大小。

最重要的是,可以找到数据段。数据段的大小是固定的,包含全局变量或静态变量。 当程序遇到特殊情况时,它可能需要额外分配数据,这就是堆发挥作用的地方,因此能够动态地增加大小。

堆栈位于虚拟地址空间的另一侧,并且(除其他外)使用 LIFO 数据结构跟踪所有函数调用。与堆类似,程序在运行时可能需要额外的空间来跟踪正在调用的新函数调用。由于堆栈位于 VAS 的另一侧,因此它向相反的方向增长,即朝向堆。

Virtual address space

TL的;博士

这就是 StackOverflow 错误发挥作用的地方。

由于堆栈向下增长(朝向堆),因此在某个时间点,它可能会进一步增长,因为它会与堆地址空间重叠。一旦发生这种情况,就会发生 StackOverflow 错误。

发生这种情况的最常见原因是由于程序中的错误,递归调用无法正确终止。


请注意,在某些系统上,VAS 的行为可能略有不同,可以分为更多的段,但是,这种一般理解适用于所有 UNIX 系统。