首页 文章

实际导致Stack Overflow错误的原因是什么? [重复]

提问于
浏览
232

这个问题在这里已有答案:

我找到了一个可靠的答案 . 根据文档,Java在以下情况下会抛出java.lang.StackOverflowError错误:

发生堆栈溢出时抛出,因为应用程序过于严重 .

但这提出了两个问题:

  • 是否有其他方法可以发生堆栈溢出,而不仅仅是通过递归?

  • StackOverflowError是否在JVM实际溢出堆栈之前或之后发生?

详细说明第二个问题:

当Java抛出StackOverflowError时,你能安全地假设堆栈没有写入堆中吗?如果你在一个抛出堆栈溢出的函数的try / catch中缩小堆栈或堆的大小,你能继续工作吗?这在任何地方记录?

答案我不是在寻找:

  • 由于错误的递归而发生StackOverflow .

  • 当堆遇到堆栈时发生StackOverflow .

10 回答

  • 27

    您可能认为stackoverflow error就像本机程序中的缓冲区溢出异常一样,当存在写入尚未分配给缓冲区的内存的风险时,从而破坏其他一些内存位置 . 事实并非如此 .

    JVM具有为每个线程的每个堆栈分配的给定内存,并且如果尝试调用方法恰好填充此内存,则JVM会抛出错误 . 就像你试图在长度为N的数组的索引N处写一样 . 没有内存损坏可能发生 . 堆栈无法写入堆 .

    StackOverflowError是堆栈中OutOfMemoryError对堆的内容:它只是表示没有更多可用内存 .

    虚拟机错误的描述(§6.3)

    StackOverflowError:Java虚拟机实现已经耗尽了线程的堆栈空间,通常是因为执行程序中的错误导致线程正在执行无限数量的递归调用 .

  • 2

    是否有其他方法可以发生堆栈溢出,而不仅仅是通过递归?

    当然 . 只需保持调用方法,而不必返回 . 但是,除非你允许递归,否则你需要很多方法 . 实际上,它没有什么区别:堆栈帧是堆栈帧,无论它是否是递归方法之一都是相同的 .

    第二个问题的答案是:当JVM尝试为下一次调用分配堆栈帧时检测到stackoverflow,并发现它不可能 . 所以,什么都不会被覆盖 .

  • 0

    是否有其他方法可以发生堆栈溢出,而不仅仅是通过递归?

    挑战接受:) StackOverflowError 没有递归(挑战失败,见评论):

    public class Test
    {
        final static int CALLS = 710;
    
        public static void main(String[] args)
        {
            final Functor[] functors = new Functor[CALLS];
            for (int i = 0; i < CALLS; i++)
            {
                final int finalInt = i;
                functors[i] = new Functor()
                {
                    @Override
                    public void fun()
                    {
                        System.out.print(finalInt + " ");
                        if (finalInt != CALLS - 1)
                        {
                            functors[finalInt + 1].fun();
                        }
                    }
                };
            }
            // Let's get ready to ruuuuuuumble!
            functors[0].fun(); // Sorry, couldn't resist to not comment in such moment. 
        }
    
        interface Functor
        {
            void fun();
        }
    }
    

    使用标准 javac Test.java 进行编译并使用 java -Xss104k Test 2> out 运行 . 在那之后, more out 会告诉你:

    Exception in thread "main" java.lang.StackOverflowError
    

    第二次尝试 .

    现在这个想法更简单了 . Java中的基元可以存储在堆栈中 . 所以,让我们声明很多双打,比如 double a1,a2,a3... . 这个脚本可以为我们编写,编译和运行the code

    #!/bin/sh
    
    VARIABLES=4000
    NAME=Test
    FILE=$NAME.java
    SOURCE="public class $NAME{public static void main(String[] args){double "
    for i in $(seq 1 $VARIABLES);
    do
        SOURCE=$SOURCE"a$i,"
    done
    SOURCE=$SOURCE"b=0;System.out.println(b);}}"
    echo $SOURCE > $FILE
    javac $FILE
    java -Xss104k $NAME
    

    而且......我有意外的事情:

    #
    # A fatal error has been detected by the Java Runtime Environment:
    #
    #  SIGSEGV (0xb) at pc=0x00007f4822f9d501, pid=4988, tid=139947823249152
    #
    # JRE version: 6.0_27-b27
    # Java VM: OpenJDK 64-Bit Server VM (20.0-b12 mixed mode linux-amd64 compressed oops)
    # Derivative: IcedTea6 1.12.6
    # Distribution: Ubuntu 10.04.1 LTS, package 6b27-1.12.6-1ubuntu0.10.04.2
    # Problematic frame:
    # V  [libjvm.so+0x4ce501]  JavaThread::last_frame()+0xa1
    #
    # An error report file with more information is saved as:
    # /home/adam/Desktop/test/hs_err_pid4988.log
    #
    # If you would like to submit a bug report, please include
    # instructions how to reproduce the bug and visit:
    #   https://bugs.launchpad.net/ubuntu/+source/openjdk-6/
    #
    Aborted
    

    这是100%重复 . 这与你的第二个问题有关:

    StackOverflowError是否在JVM实际溢出堆栈之前或之后发生?

    因此,在OpenJDK 20.0-b12的情况下,我们可以看到JVM首先爆炸 . 但它似乎是一个错误,也许有人可以在评论中确认,因为我'm not sure. Should I report this? Maybe it'已经修复了一些较新版本...根据JVM specification link(由评论中的JB Nizet给出)JVM应抛出 StackOverflowError ,而不是死:

    如果线程中的计算需要比允许的更大的Java虚拟机堆栈,则Java虚拟机会抛出StackOverflowError .


    第三次尝试 .

    public class Test {
        Test test = new Test();
    
        public static void main(String[] args) {
            new Test();
        }
    }
    

    我们想要创建新的 Test 对象 . 因此,将调用其(隐式)构造函数 . 但是,就在此之前, Test 的所有成员都已初始化 . 所以,首先执行 Test test = new Test() ...

    我们想要创建新的 Test 对象......

    更新:运气不好,这是递归,我问了关于here的问题 .

  • 3

    没有“StackOverFlowException” . 你的意思是“StackOverFlowError” .

    是的,你可以继续工作,如果你 grab 它,因为堆栈被清除,但这将是一个糟糕和丑陋的选择 .

    什么时候抛出错误? - 调用方法时,JVM会验证是否有足够的内存来执行此操作 . 当然,如果不可能,则抛出错误 .

    • 不,这是您可以获得该错误的唯一方法:让您的堆栈满 . 但不仅通过递归,还调用无限调用其他方法的方法 . 这是一个非常具体的错误,所以没有 .

    • 在堆栈已满之前抛出它,正好在验证它时 . 如果没有空间,你会把数据放在哪里?压倒他人?纳阿 .

  • -1

    StackOverFlowError的最常见原因是过深或过深无限递归 .

    例如:

    public int yourMethod(){
           yourMethod();//infinite recursion
    }
    

    在Java中:

    堆和堆栈在内存中有 two 个区域 . stack memory 用于存储局部变量和函数调用,而 heap memory 用于存储Java中的对象 .

    如果堆栈中没有剩余内存用于存储函数调用或局部变量,JVM将抛出 java.lang.StackOverFlowError

    而如果没有更多的堆空间来创建对象,JVM将抛出 java.lang.OutOfMemoryError

  • 3

    Java可以存储两个主要的地方 . 第一个是Heap,用于动态分配的对象 . new .

    此外,每个正在运行的线程都有自己的堆栈,并获得分配给该堆栈的内存量 .

    当您调用方法时,数据将被推入堆栈以记录方法调用,传入的参数以及分配的任何局部变量 . 具有五个局部变量和三个参数的方法将使用比没有局部变量的 void doStuff() 方法更多的堆栈空间 .

    堆栈的主要优点是没有内存碎片,一个方法调用的所有内容都分配在堆栈的顶部,并且从方法返回很容易 . 要从方法返回,只需将堆栈展开回上一个方法,设置返回值所需的任何值即可完成 .

    因为堆栈是每个线程的固定大小,(请注意,Java规范不需要固定大小,但是在编写时大多数JVM实现使用固定大小)并且因为每当您创建方法时都需要堆栈上的空间希望它现在应该清楚为什么它会耗尽以及什么会导致它耗尽 . 没有固定数量的方法调用,没有任何关于递归的具体内容,您将获得尝试调用方法并且内存不足的异常 .

    当然,堆栈的大小设置得足够高,以至于在常规代码中不太可能发生 . 在递归代码中虽然可以很容易地递归到很大的深度,并且在那时你开始遇到这个错误 .

  • 3

    StackOverflowError 由于应用程序递归过深而发生(这不是您期望的答案) .

    现在 StackOverflowError 发生的其他事情是继续从方法调用方法直到你得到 StackOverflowError ,但没有人可以编程获得 StackOverflowError ,即使这些程序员这样做,他们也没有遵循cyclomatic complixity的编码标准,每个程序员都必须在编程时理解 . 这样的原因'StackOverflowError'将需要很多时间来纠正它 .

    但是在不知不觉中编码导致 StackOverflowError 的一行或两行是可以理解的,JVM会抛出它,我们可以立即纠正它 . Here是我对其他一些问题的回答 .

  • 195

    当进行函数调用并且堆栈已满时,会发生StackOverflow .

    就像ArrayOutOfBoundException一样 . 它不能破坏任何东西,事实上很有可能 grab 它并从中恢复 .

    它通常是由不受控制的递归引起的,但它也可能是由于只有一个非常深的函数调用堆栈引起的 .

  • 0

    在c#中,您可以通过错误地定义对象属性以不同的方式实现堆栈溢出 . 例如 :

    private double hours;
    
    public double Hours
            {
                get { return Hours; }
                set { Hours = value; }
            }
    

    正如你所看到的,这将永远保持返回小时H的大小写,这本身将返回小时等等等等 .

    由于内存不足或使用托管语言,通常还会发生堆栈溢出,因为语言管理器(CLR,JRE)会检测到代码卡在无限循环中 .

  • 52

    但是这引出了两个问题:是否有其他方式可以发生堆栈溢出,而不仅仅是通过递归? StackOverflowError是否在JVM实际溢出堆栈之前或之后发生?

    • 当我们分配大于堆栈限制的大小时(例如 int x[10000000]; ),也会发生这种情况 .

    • 回答第二个是

    每个线程都有自己的堆栈,该堆栈为在该线程上执行的每个方法保存一个帧 . 因此,当前正在执行的方法位于堆栈的顶部 . 为每个方法调用创建一个新帧并将其添加(推送)到堆栈顶部 . 当方法正常返回时,或者在方法调用期间抛出未捕获的异常时,框架将被删除(弹出) . 除了推送和弹出框架对象之外,不直接操纵堆栈,因此框架对象可以在堆中分配,并且存储器不需要是连续的 .

    因此,通过考虑线程中的堆栈,我们可以得出结论 .

    堆栈可以是动态或固定大小 . 如果一个线程需要比允许更大的堆栈,则抛出 StackOverflowError . 如果一个线程需要一个新的帧并且没有足够的内存来分配它,那么抛出一个 OutOfMemoryError .

    你可以得到JVM here的描述

相关问题