什么是StackOverflowError?

527

什么是 StackOverflowError, 它是由什么引起的,我该如何处理它们?


Java中的堆栈大小较小。有时,例如在递归调用过多时,您会遇到此问题。您可以通过循环重新设计代码。您可以在此网址中找到通用的设计模式:http://www.jndanial.com/73/ - Danial Jalalnouri
一个不太显而易见的方法是:在某个静态上下文(例如 main 方法)中添加行 new Object() {{getClass().newInstance();}};。 从实例上下文中不起作用(仅抛出 InstantiationException)。 - John McClane
16个回答

3

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

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

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

  • (1) → 函数调用
  • (2) → 函数调用中的局部变量(包括主函数)
  • (3) → 函数调用中的局部变量(不包括主函数)
  • (4) → 函数调用
  • (5) → 通常是函数调用,对于堆栈溢出来说一般无关紧要。

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

  • 在主函数或任何被调用的函数中声明过大的局部变量(例如:int array[10000][10000];
  • 非常深或无限递归(同时进行太多函数调用)。

为避免 StackOverflowError,您可以:

  • 检查局部变量是否过大(1 MB 的数量级)→ 使用堆(malloc/calloc 调用)或全局变量。

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

  • 检查普通的过深递归 → 最简单的方法是将实现更改为迭代式。

请注意,全局变量、包含库等不使用堆栈。
只有在上述方法无效的情况下,才将堆栈大小更改为特定操作系统的最大值。例如,在Ubuntu中:ulimit -s 32768(32 MB)。 (这从未是我任何堆栈溢出错误的解决方案,但我也没有太多经验。)
我省略了C语言中的特殊或非标准情况(如alloc()的用法及类似情况),因为如果您正在使用它们,则应该已经清楚自己在做什么。

2
在紧急情况下,以下情况会导致堆栈溢出错误。
public class Example3 {

    public static void main(String[] args) {

        main(new String[1]);
    }

}

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

1
这个问题有很多好的答案。然而,我想采取稍微不同的方法,并提供一些关于内存如何工作的更深入的见解,以及一个(简化的)可视化来更好地理解 StackOverflow 错误。这种理解不仅适用于 Java,而且适用于所有类似的进程。
在现代系统中,所有新进程都会获得自己的虚拟地址空间(VAS)。实质上,VAS 是操作系统在物理内存之上提供的抽象层,以确保进程不会干扰彼此的内存。然后,内核的工作是将提供的虚拟地址映射到实际的物理地址。
VAS可以分为几个部分:
为了让CPU知道它应该做什么,必须将机器指令加载到内存中。这通常称为代码或文本段,大小固定。
除此之外,还有数据段和堆。数据段大小固定,包含全局或静态变量。当程序遇到特殊条件时,可能需要额外分配数据,这就是堆发挥作用的地方,因此它能够动态增长。
栈位于虚拟地址空间的另一侧,并且(除其他功能外)使用LIFO数据结构跟踪所有函数调用。类似于堆,程序可能需要在运行时额外的空间来跟踪新的函数调用。由于栈位于VAS的另一侧,因此它向相反方向即向堆增长。

Virtual address space

TL;DR:这是关于“StackOverflow 错误”的问题。由于堆栈向下增长(朝向堆),可能会发生某些时刻它无法继续增长,因为它会与堆地址空间重叠。一旦发生这种情况,就会出现 StackOverflow 错误。最常见的原因是程序中存在错误,导致递归调用无法正确终止。
请注意,在某些系统上,VAS 的行为可能会略有不同,并且可以分成更多的段,但是这个通用的理解适用于所有 UNIX 系统。

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


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)

这里是OpenJDK 7中StackOverflowError的源代码


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接