Java 究竟是什么导致了堆栈溢出错误?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22182669/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 14:10:27  来源:igfitidea点击:

What actually causes a Stack Overflow error?

javajvmstack-overflow

提问by retrohacker

I've looked everywhere and can't find a solid answer. According to the documentation, Java throws a java.lang.StackOverflowErrorerror under the following circumstance:

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

Thrown when a stack overflow occurs because an application recurses too deeply.

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

But this raises two questions:

但这引发了两个问题:

  • Aren't there other ways for a stack overflow to occur, not only through recursion?
  • Does the StackOverflowError happen before the JVM actually overflows the stack or after?
  • 不是只有通过递归才能发生堆栈溢出的其他方法吗?
  • StackOverflowError 是在 JVM 实际溢出堆栈之前还是之后发生?

To elaborate on the second question:

详细说明第二个问题:

When Java throws the StackOverflowError, can you safely assume that the stack did not write into the heap? If you shrink the size of the stack or heap in a try/catch on a function that throws a stack overflow, can you continue working? Is this documented anywhere?

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

Answers I am not looking for:

我不是在寻找答案:

  • A StackOverflow happens because of bad recursion.
  • A StackOverflow happens when the heap meets the stack.
  • 由于递归错误,会发生 StackOverflow。
  • 当堆遇到堆栈时会发生 StackOverflow。

采纳答案by JB Nizet

It seems you're thinking that a stackoverflow erroris like a buffer overflow exception in native programs, when there is a risk of writing into memory that had not been allocated for the buffer, and thus to corrupt some other memory locations. It's not the case at all.

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

JVM has a given memory allocated for each stack of each thread, and if an attempt to call a method happens to fill this memory, JVM throws an error. Just like it would do if you were trying to write at index N of an array of length N. No memory corruption can happen. The stack can not write into the heap.

JVM 为每个线程的每个堆栈分配了给定的内存,如果调用方法的尝试恰好填满了该内存,则 JVM 会抛出错误。就像您尝试在长度为 N 的数组的索引 N 处写入一样。不会发生内存损坏。堆栈不能写入堆。

A StackOverflowError is to the stack what an OutOfMemoryError is to the heap: it simply signals that there is no more memory available.

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

Description from Virtual Machine Errors (§6.3)

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

StackOverflowError: The Java Virtual Machine implementation has run out of stack space for a thread, typically because the thread is doing an unbounded number of recursive invocations as a result of a fault in the executing program.

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

回答by njzk2

A StackOverflow happens when a function call is made and the stack is full.

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

Just like an ArrayOutOfBoundException. It cannot corrupt anything, in fact it is very possible to catch it and recover from it.

就像 ArrayOutOfBoundException 一样。它不能破坏任何东西,实际上很有可能捕获它并从中恢复。

It usually happens as the result of an uncontrolled recursion, but it can also be caused by simply have a very deep stack of functions call.

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

回答by Ingo

Aren't there other ways for a stack overflow to occur, not only through recursion?

不是只有通过递归才能发生堆栈溢出的其他方法吗?

Sure. Just keep calling methods, without ever returning. You'll need a lot of methods, though, unless you allow recursion. Actually, it doesn't make a difference: a stack frame is a stack frame, whether it is one of a recursive method or not is the same.

当然。只是继续调用方法,永远不会返回。但是,除非您允许递归,否则您将需要很多方法。其实没什么区别:栈帧就是栈帧,是不是递归方法都是一样的。

The answer to your second question is: The stackoverflow is detected when the JVM tries to allocate the stack frame for the next call, and finds it is not possible. So, nothing will be overwritten.

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

回答by GabrielBB

There is no "StackOverFlowException". What you mean is "StackOverFlowError".

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

Yes you can continue working if you catch it because the stack is cleared when you do that but that would be a bad and ugly option.

是的,如果您抓住它,您可以继续工作,因为这样做时堆栈会被清除,但这将是一个糟糕而丑陋的选择。

When exactly the error is thrown ? - When you call a method and the JVM verifies if there is enough memory to do it. Of course, the error is thrown if it's not possible.

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

  • No, that is the only way you can get that error: getting your stack full. But not only through recursion, also calling methods that infinitely call other methods. It's a very specific error so no.
  • It is thrown before the stack is full, exactly when you verify it. Where would you put the data if there is no space available ? Overriding others ? Naah.
  • 不,这是您获得该错误的唯一方法:让您的堆栈已满。但不仅通过递归,还调用无限调用其他方法的方法。这是一个非常具体的错误,所以没有。
  • 它在堆栈已满之前抛出,正是在您验证它时。如果没有可用空间,您会将数据放在哪里?凌驾于他人之上?呐。

回答by Adam Stelmaszczyk

Aren't there other ways for a stack overflow to occur, not only through recursion?

不是只有通过递归才能发生堆栈溢出的其他方法吗?

Challenge accepted :) StackOverflowErrorwithout recursion(challenge failed, see comments):

接受挑战 :)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();
    }
}

Compile with standard javac Test.javaand run with java -Xss104k Test 2> out. After that, more outwill tell you:

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

Exception in thread "main" java.lang.StackOverflowError


Second try.

第二次尝试。

Now the idea is even simpler. Primitives in Java can be stored on the stack. So, let's declare a lot of doubles, like double a1,a2,a3.... This script can write, compile and run the codefor us:

现在这个想法更简单了。Java 中的原语可以存储在堆栈中。所以,让我们声明很多双打,比如double 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

And... I got something unexpected:

而且......我得到了一些意想不到的东西:

#
# 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

It's 100% repetitive. This is related to your second question:

它是 100% 重复的。这与您的第二个问题有关:

Does the StackOverflowError happen before the JVM actually overflows the stack or after?

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

So, in case of OpenJDK 20.0-b12 we can see that JVM firstly exploded. But it seems like a bug, maybe someone can confirm that in comments please, because I'm not sure. Should I report this? Maybe it's already fixed in some newer version... According to JVM specification link(given by JB Nizetin a comment) JVM should throw a StackOverflowError, not die:

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

If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.

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



Third try.

第三次尝试。

public class Test {
    Test test = new Test();

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

We want to create new Testobject. So, its (implicit) constructor will be called. But, just before that, all the members of Testare initialized. So, Test test = new Test()is executed first...

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

We want to create new Testobject...

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

Update: Bad luck, this is recursion, I asked question about that here.

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

回答by Tim B

There are two main places that things can be stored in Java. The first is the Heap, that's used for dynamically allocated objects. new.

有两个主要的地方可以在 Java 中存储东西。第一个是堆,用于动态分配的对象。new.

In addition each running thread gets its own stack, and it gets an amount of memory allocated to that stack.

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

When you call a method then data is pushed into the stack to record the method call, the parameters being passed in, and any local variables being allocated. A method with five local variables and three parameters will use more stack space than a void doStuff()method with no local variables will.

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

The main advantages of the stack are that there is no memory fragmentation, everything for one method call is allocated on the top of the stack, and that returning from methods is easy. To return from a method you just unwind the stack back to the previous method, set any value needed for the return value and you are done.

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

Because the stack is a fixed size per thread, (note that the Java Spec does not require a fixed size, but most JVM implementations at the time of writing use a fixed size) and because space on the stack is needed whenever you make a method call hopefully it should now be clear why it can run out and what can cause it to run out. There isn't a fixed number of method calls, there isn't anything specific about recursion, you get the exception which you try to call a method and there isn't enough memory.

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

Of course the size of stacks is set high enough that it is highly unlikely to happen in regular code. In recursive code though it can be quite easy to recurse to huge depths, and at that point you start running into this error.

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

回答by eatSleepCode

But this raises two questions:

  1. Aren't there other ways for a stack overflow to occur, not only through recursion?
  2. Does the StackOverflowError happen before the JVM actually overflows the stack or after?

但这引发了两个问题:

  1. 不是只有通过递归才能发生堆栈溢出的其他方法吗?
  2. StackOverflowError 是在 JVM 实际溢出堆栈之前还是之后发生?
  1. It can also occur when we are Allocating size greater than stack's limit (for eg. int x[10000000];).

  2. Answer to second is

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

  2. 第二个答案是

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.

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

So by considering stack in a Thread we can conclude.

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

A stack can be a dynamic or fixed size. If a thread requires a larger stack than allowed a StackOverflowErroris thrown. If a thread requires a new frame and there isn't enough memory to allocate it then an OutOfMemoryErroris thrown.

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

you can get description for JVM here

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

回答by AmitG

StackOverflowErroroccurs due to an application recurses too deeply (This is not an answer you are expecting).

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

Now other things to happen to StackOverflowErroris keep calling methods from methods till you get StackOverflowError, but nobody can program to get StackOverflowErrorand even if those programmer are doing so then they are not following coding standards for cyclomatic complixitythat every programmer has to understand while programming. Such reason for 'StackOverflowError' will require much time to rectify it.

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

But unknowingly coding one line or two line which causes StackOverflowErroris understandable and JVM throws that and we can rectify it instantly. Hereis my answer with picture for some other question.

但是在不知不觉中编写一行或两行代码StackOverflowError是可以理解的,JVM 会抛出它,我们可以立即纠正它。是我对其他问题的图片回答。

回答by Lazy Coder

The most common cause of StackOverFlowError is excessively deep or infinite recursion.

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

For instance:

例如:

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

In Java:

在 Java 中:

There are twoareas in memory the heap and stack. The stack memoryis used to store local variables and function call, while heap memoryis used to store objects in Java.

two在存储区堆和栈。该stack memory用于存储局部变量和函数调用,而heap memory用于在Java中存储对象。

If there is no memory left in stack for storing function call or local variable, JVM will throw java.lang.StackOverFlowError

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

while if there is no more heap space for creating object, JVM will throw java.lang.OutOfMemoryError

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

回答by Bryan Arbelo - MaG3Stican

In c# you can achieve stack overflow in a different manner, by wrongly defining object properties. For example :

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

private double hours;

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

As you can see this will forever keep on returning Hours with an uppercase H , which in itself will return Hours and so and so on.

正如您所看到的,这将永远继续以大写的 H 返回 hours ,它本身将返回 hours 等等。

A stack overflow will often occur also because of running out of memory or when using managed languages because your language manager (CLR, JRE) will detect that your code got stuck in an infinite loop.

由于内存不足或使用托管语言时,通常会发生堆栈溢出,因为您的语言管理器(CLR、JRE)会检测到您的代码陷入无限循环。