【问题标题】:What is a StackOverflowError?什么是 StackOverflowError?
【发布时间】:2016-10-04 01:00:20
【问题描述】:

什么是StackOverflowError,是什么原因造成的,我应该如何处理?

【问题讨论】:

  • java 中的堆栈大小很小。而有些时候,比如很多递归调用,你会面临这个问题。您可以通过循环重新设计代码。你可以在这个 url 中找到通用的设计模式:jndanial.com/73
  • 一种不明显的获取方法:将行 new Object() {{getClass().newInstance();}}; 添加到一些静态上下文(例如 main 方法)。在实例上下文中不起作用(仅抛出 InstantiationException)。

标签: java exception memory-leaks out-of-memory stack-overflow


【解决方案1】:

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

您的进程还有一个,它位于进程的底部端。当你分配内存时,这个堆会增长到地址空间的上端。如您所见,堆有可能与堆栈“碰撞”(有点像构造板块!!!)。

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

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

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

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

【讨论】:

  • 原始海报:嘿,这太棒了。那么递归总是负责堆栈溢出?或者其他事情也可以对他们负责?不幸的是,我正在使用一个库......但不是我理解的。
  • 哈哈哈,原来是这样:while (points
  • 不,如果您在en.wikipedia.org/wiki/Stack_overflow 上查找有关它的维基百科文章,堆栈溢出也可能来自于变量太大而无法在堆栈上分配。
  • 应该指出,几乎不可能“处理”堆栈溢出错误。在大多数环境中,要处理错误需要在堆栈上运行代码,如果没有更多堆栈空间,这很困难。
  • @JB King:实际上不适用于 Java,Java 中只有原始类型和引用保存在堆栈中。所有大的东西(数组和对象)都在堆上。
【解决方案2】:

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

局部变量存储在堆栈

如果您查看图像,您应该能够理解事情是如何运作的。

当 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 标志将线程堆栈的大小指定为等于 1 MB 的示例执行如下所示:

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)安装,默认线程堆栈大小可能 等于 512 KB 或 1 MB。可以增加线程栈 使用-Xss 标志的大小。这个标志可以通过指定 项目的配置,或通过命令行。的格式 -Xss 参数是: -Xss<size>[g|G|m|M|k|K]

【讨论】:

  • 在使用 -Xss 参数仅对新线程生效的 windows 时,某些 java 版本中似乎存在错误
【解决方案3】:

如果你有这样的功能:

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

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

【讨论】:

  • 错了。你的函数是尾递归的。大多数编译语言都有尾递归优化。这意味着递归简化为一个简单的循环,并且在某些系统上您永远不会使用这段代码遇到堆栈溢出。
  • 天哪,哪些非函数式语言支持尾递归?
  • @banister 和一些 javascript 实现
  • @horseyguy Scala 支持尾递归。
  • 这抓住了造成堆栈溢出的本质。不错。
【解决方案4】:

堆栈溢出的确切含义是:堆栈溢出。通常程序中有一个堆栈,其中包含局部范围的变量和在例程执行结束时返回的地址。该堆栈往往是内存中某处的固定内存范围,因此它可以包含多少值是有限的。

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

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

所以堆栈溢出出现在你分配太多到堆栈中的地方。例如,在提到的递归中。

一些实现优化了某些形式的递归。特别是尾递归。尾递归例程是例程的形式,其中递归调用作为例程所做的最后一件事出现。这样的例行调用被简单地简化为跳转。

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

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

【讨论】:

  • 有栈underflow这样的东西吗?
  • 在汇编中可能会出现堆栈下溢(弹出的次数多于推送的次数),但在编译语言中这几乎是不可能的。我不确定,您也许能够找到“支持”负大小的 C 语言 alloca() 的实现。
  • 堆栈溢出的意思就是:堆栈溢出。通常程序中有一个包含局部范围变量的堆栈 -> 不,每个线程都有自己的堆栈,其中包含每个包含局部变量的方法调用的堆栈帧..
【解决方案5】:

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

【讨论】:

  • 糟糕,没有看到 Java 标签
  • 另外,来自这里的原始海报:嵌套函数太深了?其他功能?并且:如何将内存分配给堆栈或堆(因为,你知道,我已经清楚地在不知道的情况下做了这些事情之一)。
  • @Ziggy:是的,如果一个函数调用另一个函数,而另一个函数又调用另一个函数,以此类推,经过多层之后,您的程序将发生堆栈溢出。 [继续]
  • [...continued] 在 Java 中,您不能直接从堆栈中分配内存(而在 C 中,您可以,这将是值得关注的事情),因此不太可能成为原因。在 Java 中,所有直接分配都来自堆,使用“new”。
  • @ChrisJester-Young 如果我在一个方法中有 100 个局部变量,这不是真的,所有它都会毫无例外地进入堆栈吗?
【解决方案6】:

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

当你的函数调用嵌套太深时,通常会发生堆栈溢出错误。请参阅Stack Overflow Code Golf 线程以获取有关如何发生这种情况的一些示例(尽管在该问题的情况下,答案会故意导致堆栈溢出)。

【讨论】:

  • 我完全想添加代码,但由于我不知道导致堆栈溢出的原因,我不确定要添加什么代码。添加所有代码会很蹩脚,不是吗?
  • 你的项目是开源的吗?如果是这样,只需创建一个 Sourceforge 或 github 帐户,然后将所有代码上传到那里。 :-)
  • 这听起来是个好主意,但我是个菜鸟,我什至不知道我要上传什么。就像,我正在导入我正在扩展的类的库等等……对我来说都是未知数。哦,伙计:糟糕的时光。
【解决方案7】:

堆栈溢出的最常见原因是递归过深或无限递归。如果这是您的问题,this tutorial about Java Recursion 可以帮助您了解问题。

【讨论】:

    【解决方案8】:

    StackOverflowError 是堆栈,OutOfMemoryError 是堆。

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

    以下示例生成StackOverflowError

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

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

    【讨论】:

      【解决方案9】:

      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】:

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

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

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

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

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

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

        要避免StackOverflowError,您可以:

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

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

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

        还要注意全局变量、包含库等...不要使用堆栈。

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

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

        【讨论】:

          【解决方案11】:

          这是一个用于反转单链表的递归算法的示例。在笔记本电脑上(规格为 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);
          }
          

          【讨论】:

          • 我认为对于 JVM,您的笔记本电脑的规格实际上并不重要。
          【解决方案12】:

          在紧要关头,以下情况会带来堆栈溢出错误。

          public class Example3 {
          
              public static void main(String[] args) {
          
                  main(new String[1]);
              }
          
          }
          

          【讨论】:

            【解决方案13】:

            一个由于递归调用错误而导致 java.lang.StackOverflowError 的简单 Java 示例:

            class Human {
                Human(){
                    new Animal();
                }
            }
            
            class Animal extends Human {
                Animal(){
                    super();
                }
            }
            
            public class Test01 {
                public static void main(String[] args) {
                    new Animal();
                }
            }
            

            【讨论】:

              【解决方案14】:

              这是一个例子

              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】:

                这是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)
                

                Here is the source code of StackOverflowError in OpenJDK 7.

                【讨论】:

                  猜你喜欢
                  • 2015-09-08
                  • 1970-01-01
                  • 2016-09-11
                  • 2012-08-08
                  相关资源
                  最近更新 更多