什么实际上导致堆栈溢出错误?
我到处寻找,找不到一个坚实的答案。 根据文档,Java在下列情况下抛出java.lang.StackOverflowError错误:
由于应用程序recursion太深而发生堆栈溢出时抛出。
但是这提出了两个问题:
- 没有其他的方式来发生堆栈溢出,不仅通过recursion?
- 在JVM实际溢出堆栈之前或之后,StackOverflowError是否发生?
详细解释第二个问题:
当Java抛出StackOverflowError时,你能否安全地认为堆栈没有写入堆中? 如果你缩小了堆栈的大小,或者堆栈溢出的函数的try / catch,你可以继续工作吗? 这是logging在任何地方?
解决scheme我不是在找:
- 一个StackOverflow发生,因为recursion不好。
- StackOverflow在堆满足堆栈时发生。
看起来你认为一个堆栈溢出错误就像本地程序中的一个缓冲区溢出exception,当存在写入未分配给缓冲区的内存的风险,从而损坏一些其他内存位置。 事实并非如此。
JVM为每个线程的每个堆栈分配一个给定的内存,如果试图调用一个方法来填充这个内存,JVM会抛出一个错误。 就像它试图写入长度为N的数组的索引N一样。不会发生内存损坏。 堆栈不能写入堆中。
一个StackOverflowError是堆栈的一个OutOfMemoryError堆:它只是表示没有更多的内存可用。
虚拟机错误说明(第6.3节)
StackOverflowError :Java虚拟机实现已经耗尽了线程的堆栈空间,通常是因为线程正在执行无限次的recursion调用,这是由于正在执行的程序中出现错误。
没有其他的方式来发生堆栈溢出,不仅通过recursion?
当然。 只要保持调用方法,永不返回。 除非你允许recursion,否则你将需要很多方法。 实际上,它并没有什么区别:一个堆栈帧是一个堆栈帧,不pipe是recursion方法还是不相同。
第二个问题的答案是:当JVM试图为下一次调用分配栈帧时发现stackoverflow是不可能的。 所以,什么都不会被覆盖。
没有其他的方式来发生堆栈溢出,不仅通过recursion?
挑战接受:) StackOverflowError
没有recursion (挑战失败,见评论):
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...
这个脚本可以为我们编写,编译和运行代码 :
#!/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首先爆炸了。 但是,这似乎是一个错误,也许有人可以在评论中确认,因为我不确定。 我应该报告吗? 也许它已经修复了一些更新的版本…根据JVM规范链接 (由JB Nizet在评论中给出)JVM应该抛出一个StackOverflowError
,而不是死:
如果线程中的计算需要比允许的更大的Java虚拟机堆栈,则Java虚拟机会引发StackOverflowError。
第三次尝试。
public class Test { Test test = new Test(); public static void main(String[] args) { new Test(); } }
我们要创build新的Test
对象。 所以,它的(隐式)构造函数将被调用。 但是,在此之前, Test
所有成员都被初始化了。 所以, Test test = new Test()
首先被执行…
我们要创build新的Test
对象…
更新:运气不好,这是recursion,我在这里问了一个问题。
StackOverFlowError最常见的原因是recursion太深或无限。
例如:
public int yourMethod(){ yourMethod();//infinite recursion }
在Java中:
内存中有two
区域堆和堆栈。 stack memory
用于存储局部variables和函数调用,而heap memory
用于存储Java中的对象。
如果堆栈中没有剩余内存用于存储函数调用或局部variables,则JVM将抛出java.lang.StackOverFlowError
而如果没有更多的堆空间来创build对象,JVM将抛出java.lang.OutOfMemoryError
没有“StackOverFlowException”。 你的意思是“StackOverFlowError”。
是的,你可以继续工作,如果你抓住它,因为当你这样做时堆栈被清除,但这将是一个糟糕和丑陋的select。
何时抛出错误? – 调用方法时,JVMvalidation是否有足够的内存来执行此操作。 当然,如果这是不可能的,则会抛出错误。
- 不,那是你唯一可以得到这个错误的方法:让你的堆栈满。 但不仅通过recursion,还要调用无限调用其他方法的方法。 这是一个非常具体的错误,所以没有。
- 它在堆栈满之前被抛出,正好在你validation的时候。 如果没有可用的空间,你会把数据放在哪里? 压倒别人? 纳阿。
在Java中可以存储两个主要的地方。 第一个是堆,用于dynamic分配的对象。 new
。
另外,每个正在运行的线程都获得自己的堆栈,并获得分配给该堆栈的大量内存。
当你调用一个方法时,数据被压入堆栈以logging方法调用,传入的参数以及被分配的任何局部variables。 具有五个局部variables和三个参数的方法将比没有局部variables的void doStuff()
方法使用更多的栈空间。
堆栈的主要优点是没有内存碎片,一个方法调用的所有内容都被分配在堆栈的顶部,并且从方法返回很容易。 要从方法返回,只需将堆栈展开回前一个方法,就可以设置返回值所需的任何值,然后完成。
由于每个线程的堆栈大小是固定的(请注意,Java Spec不需要固定的大小,但是大多数写入时的JVM实现使用固定的大小),并且因为每当创build方法时都需要堆栈空间希望现在应该清楚为什么它可能会用完,什么会导致它用完。 没有固定数量的方法调用,没有任何关于recursion的具体内容,您会得到您尝试调用某个方法的exception,并且内存不足。
当然,堆栈的大小设置得足够高,以至于不太可能在普通代码中发生。 在recursion代码中,虽然可以很容易地recursion到深度,并且在这一点上,你开始遇到这个错误。
发生StackOverflowError
是由于应用程序recursion太深(这不是您所期望的答案)。
现在StackOverflowError
发生的其他事情是继续从方法调用方法,直到你得到StackOverflowError
,但没有人可以编程获得StackOverflowError
,即使这些程序员这样做,然后他们没有遵循循环complixity的编码标准,每个程序员必须了解节目。 “StackOverflowError”的这种原因将需要很多时间来纠正它。
但不知不觉编码一行或两行会导致StackOverflowError
是可以理解的,JVM会抛出这个错误,我们可以立即纠正它。 这是我的答案与其他问题的图片。
当进行函数调用并且堆栈已满时,将发生StackOverflow。
就像一个ArrayOutOfBoundException。 它不能腐败任何东西,事实上它很有可能被捕获并从中恢复过来。
它通常是由于不受控制的recursion而产生的,但也可能是由于函数调用非常深入而导致的。
在C#中,可以通过错误地定义对象属性来以不同的方式实现堆栈溢出。 例如 :
private double hours; public double Hours { get { return Hours; } set { Hours = value; } }
正如你所看到的,这将永远持续返回小时,大写H,本身将返回小时等等。
由于语言pipe理器(CLR,JRE)会检测到代码被无限循环卡住,因此内存溢出或使用托pipe语言时,经常会发生堆栈溢出。
但是这提出了两个问题:
- 没有其他的方式来发生堆栈溢出,不仅通过recursion?
- 在JVM实际溢出堆栈之前或之后,StackOverflowError是否发生?
-
当我们分配的大小大于堆栈的限制时(例如,
int x[10000000];
),也会发生这种情况。 -
第二个答案是
每个线程都有自己的堆栈,为每个在该线程上执行的方法保存一个框架。 所以当前正在执行的方法在堆栈的顶部。 为每个方法调用创build一个新框架并将其添加(推送)到栈顶。 当方法正常返回时,框架被移除(popup),或者在方法调用期间抛出未捕获的exception。 除了推送和popup框架对象之外,堆栈不是直接操作的,因此框架对象可以在堆中分配,并且存储器不需要是连续的。
所以通过考虑一个线程的堆栈,我们可以得出结论。
堆栈可以是dynamic的或固定的大小。 如果一个线程需要比允许的更大的堆栈,则抛出StackOverflowError
。 如果一个线程需要一个新的框架,并没有足够的内存来分配它,那么抛出一个OutOfMemoryError
。
你可以在这里获得JVM的描述