我到处寻找,找不到可靠的答案。根据文档,Java在以下情况下抛出java.lang.StackOverflowError错误:
当由于应用程序过于冗长而发生堆栈溢出时抛出。
但这提出了两个问题:
详细说明第二个问题:
当Java抛出StackOverflowError时,你能安全地假设堆栈没有写入堆中吗?如果你在一个抛出堆栈溢出的函数的try / catch中缩小堆栈或堆的大小,你能继续工作吗?这是在任何地方记录的吗?
答案我不是在找:
答案 0 :(得分:195)
似乎你认为stackoverflow error就像本机程序中的缓冲区溢出异常一样,当存在写入内存但没有为缓冲区分配的风险时,会破坏其他一些记忆位置。事实并非如此。
JVM具有为每个线程的每个堆栈分配的给定内存,并且如果尝试调用方法恰好填充此内存,则JVM会抛出错误。就像你试图在长度为N的数组的索引N处写一样。没有内存损坏可能发生。堆栈无法写入堆中。
StackOverflowError是堆栈OutOfMemoryError对堆的唯一信号:它只是表示没有更多可用内存。
StackOverflowError :Java虚拟机实现已经耗尽了线程的堆栈空间,通常是因为执行程序中的错误导致线程正在执行无限数量的递归调用。
答案 1 :(得分:52)
是否有其他方法可以发生堆栈溢出,而不仅仅是通过递归?
不确定。只需保持调用方法,而不必返回。但是,除非你允许递归,否则你需要很多方法。实际上,它没有什么区别:堆栈帧是一个堆栈帧,无论它是否是递归方法之一都是相同的。
第二个问题的答案是:当JVM尝试为下一次调用分配堆栈帧时检测到堆栈溢出,并发现它不可能。所以,什么都不会被覆盖。
答案 2 :(得分:27)
不仅存在发生堆栈溢出的其他方法 通过递归?
挑战接受:) 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首先爆炸。但它似乎是一个错误,也许有人可以在评论中确认,因为我不确定。我应该报告这个吗?也许它已经修复了一些较新的版本...根据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 :(得分:3)
没有“StackOverFlowException”。你的意思是“StackOverFlowError”。
是的,你可以继续工作,如果你抓住它,因为当你这样做时,堆栈被清除,但这将是一个糟糕和丑陋的选择。
何时抛出错误? - 调用方法时,JVM会验证是否有足够的内存来执行此操作。当然,如果不可能,就会抛出错误。
答案 4 :(得分:3)
Java可以存储两个主要的地方。第一个是Heap,用于动态分配的对象。 new
。
此外,每个正在运行的线程都有自己的堆栈,它会获得分配给该堆栈的内存量。
当您调用方法时,数据将被推入堆栈以记录方法调用,传入的参数以及分配的任何局部变量。具有五个局部变量和三个参数的方法将使用比没有局部变量的void doStuff()
方法更多的堆栈空间。
堆栈的主要优点是没有内存碎片,一个方法调用的所有内容都分配在堆栈顶部,并且从方法返回很容易。要从方法返回,只需将堆栈展开回上一个方法,设置返回值所需的任何值即可完成。
因为每个线程的堆栈是固定大小的(注意Java规范不需要固定大小,但是在编写时大多数JVM实现使用固定大小)并且因为每当你需要堆栈空间希望现在应该清楚为什么它可以耗尽以及导致它耗尽的原因。没有固定数量的方法调用,没有任何关于递归的具体内容,你得到了你试图调用方法并且内存不足的异常。
当然,堆栈的大小设置得足够高,以至于在常规代码中不太可能发生。在递归代码中虽然可以很容易地递归到很大的深度,然后你就会开始遇到这个错误。
答案 5 :(得分:3)
StackOverFlowError的最常见原因是过度深度或无限递归。
例如:
public int yourMethod(){
yourMethod();//infinite recursion
}
在Java中:
内存中有two
个堆和堆栈。 stack memory
用于存储局部变量和函数调用,而heap memory
用于存储Java中的对象。
如果堆栈中没有剩余内存用于存储函数调用或局部变量,JVM将抛出java.lang.StackOverFlowError
如果没有更多的堆空间来创建对象,JVM将抛出java.lang.OutOfMemoryError
答案 6 :(得分:2)
StackOverflowError
由于应用程序过于冗长而发生(这不是您期望的答案)。
现在StackOverflowError
发生的其他事情是继续从方法调用方法直到你得到StackOverflowError
,但没有人可以编程来获取StackOverflowError
,即使这些程序员正在这样做,那么他们不遵循编程时每个程序员必须理解的cyclomatic complixity编码标准。 'StackOverflowError'的原因需要很长时间来纠正它。
但是在不知不觉中编码导致StackOverflowError
的一行或两行是可以理解的,JVM会抛出它,我们可以立即纠正它。 Here是我对其他问题的回答。
答案 7 :(得分:0)
当进行函数调用并且堆栈已满时,会发生StackOverflow。
就像ArrayOutOfBoundException一样。它不能破坏任何东西,事实上很有可能抓住它并从中恢复。
它通常是由不受控制的递归引起的,但它也可能是由于只是有一个非常深的函数调用堆栈引起的。
答案 8 :(得分:0)
在c#中,您可以通过错误地定义对象属性以不同的方式实现堆栈溢出。 例如:
private double hours;
public double Hours
{
get { return Hours; }
set { Hours = value; }
}
正如你所看到的,这将永远保持以大写字母H返回小时数,这本身将返回小时数等等。
由于内存不足或使用托管语言,也会经常发生堆栈溢出,因为语言管理器(CLR,JRE)会检测到代码卡在无限循环中。
答案 9 :(得分:-1)
但这提出了两个问题:
- 是否有其他方式可以发生堆栈溢出,而不仅仅是通过递归?
- StackOverflowError是否在JVM实际溢出堆栈之前或之后发生?
醇>
当我们分配大于堆栈限制的大小时(例如。int x[10000000];
),也会发生这种情况。
第二个答案是
每个线程都有自己的堆栈,该堆栈为在该线程上执行的每个方法保存一个帧。因此,当前正在执行的方法位于堆栈的顶部。为每个方法调用创建一个新帧并将其添加(推送)到堆栈顶部。当方法正常返回时,或者在方法调用期间抛出未捕获的异常时,框架将被删除(弹出)。除了推送和弹出框架对象之外,不会直接操作堆栈,因此可以在堆中分配框架对象,并且内存不需要是连续的。
因此,通过考虑线程中的堆栈我们可以得出结论。
堆栈可以是动态或固定大小。如果一个线程需要比允许更大的堆栈,则抛出StackOverflowError
。如果一个线程需要一个新的帧并且没有足够的内存来分配它,那么就会抛出OutOfMemoryError
。
您可以获得JVM here
的说明