我到处找都找不到确切的答案。根据文档,Java在以下情况下抛出Java .lang. stackoverflowerror错误:

当由于应用程序递归太深而发生堆栈溢出时引发。

但这引发了两个问题:

除了通过递归,没有其他方法可以导致堆栈溢出吗? StackOverflowError发生在JVM溢出堆栈之前还是之后?

关于第二个问题:

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

我不想要的答案:

发生StackOverflow是由于糟糕的递归。 当堆遇到堆栈时发生StackOverflow。


当前回答

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

现在其他的事情发生在StackOverflowError是从方法一直调用方法,直到你得到StackOverflowError,但没有人可以编程得到StackOverflowError,即使那些程序员是这样做的,然后他们没有遵循编码标准的圈的复杂性,每个程序员都必须理解,而编程。这样的原因'StackOverflowError'将需要很多时间来纠正它。

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

其他回答

似乎您认为stackoverflow错误类似于本机程序中的缓冲区溢出异常,此时存在将未分配给缓冲区的内容写入内存的风险,从而破坏一些其他内存位置。根本不是这样的。

JVM为每个线程的每个堆栈分配了一个给定的内存,如果试图调用一个方法恰好填满了这个内存,JVM就会抛出一个错误。就像你想写一个长度为N的数组的下标N一样,不会发生内存损坏。堆栈不能写入堆。

StackOverflowError之于堆栈,就像OutOfMemoryError之于堆:它只是表明没有更多可用内存。

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

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

StackOverflow发生在函数调用时,堆栈已满。

就像一个ArrayOutOfBoundException。它不会腐蚀任何东西,事实上很有可能抓住它并从中恢复。

它通常是由于不受控制的递归导致的,但也可能是由于调用了非常深的函数堆栈而导致的。

就没有其他方式发生堆栈溢出了吗 通过递归?

没有递归的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>输出运行。在那之后,更多的信息会告诉你:

Exception in thread "main" java.lang.StackOverflowError

第二次尝试。

现在这个想法更简单了。Java中的原语可以存储在堆栈上。我们声明很多双精度数,比如双精度a1 a2 a3....这个脚本可以为我们编写,编译和运行代码:

#!/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%的重复。这和你第二个问题有关。

是否在JVM实际溢出之前发生StackOverflowError 叠完还是之后?

因此,在OpenJDK 20.0-b12的情况下,我们可以看到JVM首先爆发了。但这似乎是一个bug,也许有人可以在评论中确认,因为我不确定。我应该报告吗?也许它已经在一些更新的版本中修复了……根据JVM规范链接(由JB Nizet在评论中给出),JVM应该抛出StackOverflowError,而不是die:

如果线程中的计算需要一个更大的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对象…

更新:运气不好,这是递归,我在这里问了一个问题。

除了通过递归,没有其他方法可以导致堆栈溢出吗?

当然。只需要继续调用方法,而不返回。不过,您将需要很多方法,除非您允许递归。实际上,这并没有什么区别:一个堆栈框架就是一个堆栈框架,不管它是不是递归方法之一都是一样的。

第二个问题的答案是:当JVM试图为下一个调用分配堆栈帧时,发现不可能分配堆栈帧,就会检测到stackoverflow。所以,没有东西会被覆盖。

但这引发了两个问题: 除了通过递归,没有其他方法可以导致堆栈溢出吗? StackOverflowError发生在JVM溢出堆栈之前还是之后?

当我们分配的大小大于栈的限制时(例如。int x[10000000];)。 第二个答案是

Each thread has its own stack that holds a frame for each method executing on that thread. So the currently executing method is at the top of the stack. A new frame is created and added (pushed) to the top of stack for every method invocation. The frame is removed (popped) when the method returns normally or if an uncaught exception is thrown during the method invocation. The stack is not directly manipulated, except to push and pop frame objects, and therefore the frame objects may be allocated in the Heap and the memory does not need to be contiguous.

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

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

你可以在这里获得JVM的描述