最后没有抛出堆栈溢出异常

时间:2015-12-03 05:29:56

标签: java exception exception-handling stack-overflow

据我所知,这段代码应该抛出StackOverflowError,但它不是。可能是什么原因?

public class SimpleFile {

    public static void main(String[] args) {
        System.out.println("main");

        try{
        SimpleFile.main(args);
            }

        catch(Exception e){
            System.out.println("Catch");
        }

        finally{
            SimpleFile.main(args);
        }
    }

}

5 个答案:

答案 0 :(得分:5)

Error不是Exception。因此捕获任何异常都不会捕获StackOverflowError。

所以让我们先解决“明显的错误” - (这段代码在本回答的后面部分是不可取的)

    catch(Throwable e){
        System.out.println("Catch");
    }

如果进行此更改,您会发现代码仍无法打印。但它没有打印出一个非常不同的原因......

强烈建议不要抓住任何ERROR(包括StackOverflowError)。但是在这里你不仅要抓住一个,而是抓住一个,因为它发生在堆栈顶部。即使使用您的代码(没有上述更改),finally块也会有效地捕获错误。

当堆栈已满并且您尝试向其添加更多内容时,会发生StackOverflowError。因此,当您捕获错误时,堆栈仍然已满。您无法调用任何方法(甚至打印到控制台),因为堆栈已满。因此,在成功打印之前,StackOverflowError会抛出第二个catch

结果就是:

  1. 捕获错误
  2. 尝试打印错误
  3. 导致另一个错误,因为它无法打印
  4. 调用finally,因为最终始终调用。
  5. 导致另一个错误,因为它无法调用main
  6. 将错误级联回上一次调用,该调用遇到同样的错误。
  7. 这里的关键是最终它将开始打印一些东西。但是对print的调用使用了大量的堆栈空间,并且在释放足够的堆栈空间以进行打印之前,您的代码必须通过以上几点重复并错误很长时间 。根据Holger对Oracle Java 8的评论,main堆栈帧所需的println堆栈帧数接近50。

    250 = 1,125,899,906,842,624

    这就是为什么你不应该抓住错误

    只有少数借口可以让你违反这条规则,并且你已经发现了如果你破坏它可能会出错的第一手资料。

答案 1 :(得分:3)

实际上你有java.lang.Stackoverflow

您可以运行此示例代码:

NSUserDefaults

PS

更多详细信息:您的程序会打印大量public class SimpleFile { public static void main(String[] args) { System.out.println("main "); try{ SimpleFile.main(args); }finally{ try{ SimpleFile.main(args); }catch(Error e2){ System.out.println("finally"); throw e2; } } } } 消息,然后在第一次收到堆栈溢出错误并转到finally块。这意味着你减少了堆栈大小,现在你可以调用一些东西。但是你在finally块中调用自己并再次获得堆栈溢出。最令我惊讶的是输出不稳定:

main

答案 2 :(得分:3)

首先,您有一个catch子句无法捕获Error s:

catch(Exception e){
    System.out.println("Catch");
}

由于Error不是Exception s,因此不会捕获StackOverflowError,并且不会执行print语句。如果没有捕获Error,则它的堆栈跟踪将由线程的默认处理程序打印,如果它到达该点。但你有另一个条款:

finally{
    SimpleFile.main(args);
}

finally子句的代码将始终在try块完成时执行,无论是正常还是异常。由于您的try块包含无限递归,因此它永远不会正常完成。

在特殊情况下,即当抛出StackOverflowError时,finally动作将再次进入无限递归,最终可能会再次失败StackOverflowError,但是它具有相同的finally块,它也将再次进入无限递归。

你的程序基本上是说“执行无限递归,然后是另一个无限递归”。请注意,您无法区分"main"打印程序是在主要无限递归中运行还是从finally块中触发的程序(除非如果堆栈溢出正好发生,可能会丢失换行符)在println执行之间。

因此,如果我们假设特定JVM具有1000嵌套调用的限制,则您的程序将对2¹⁰⁰⁰方法quantification进行main次调用。由于您的main方法实际上什么都不做,优化器甚至可以忽略这些令人难以置信的调用次数,但是这种优化还意味着所需的堆栈大小消失,因此,更高数量的递归调用成为可能。只有一个JVM实现强制对受支持的递归调用数量进行有意限制,与实际所需的堆栈空间无关,可以强制该程序永久终止。

但是请注意,在无限递归的情况下,根本没有保证获得StackOverflowError。理论上,具有无限堆栈空间的JVM将是有效的实现。这意味着JVM实际上优化了递归代码,无需额外的堆栈空间即可运行,也是有效的。

因此,对于像Oracle的JVM这样的典型实现,您的程序几乎不可能报告StackOverflowError。它们会发生,但会被finally块中的后续递归所影响,因此从未报告过。

答案 3 :(得分:0)

我对您的代码进行了一些修改并完成了一些测试。我仍然无法找到你的问题的答案。最后一块肯定会导致代码的整个疲惫行为。

public class SimpleFile {

    static int i = 0;

    public static void main(String[] args) {
        int c = i++;
        System.out.println("main" + i);

        try {
            SimpleFile.main(args);
        }

        catch (Throwable e) {
            System.out.println("Catch" + e);
        }

        finally {
            if (i < 30945) {
                System.out.println("finally" + c);
                SimpleFile.main(args);
            }
        }
    }
}

输出是...我只是显示最后一行:

main30941
main30942finally30940
main30943finally30927
main30944
main30945
main30946
main30947Catchjava.lang.StackOverflowError

我想证明即使是静态方法也会在递归调用时获得StackOverflowError。因为你正在捕获永远不会捕获错误的异常。

如果以递归方式调用main,请参阅有关StackOverflowError的Call Main Recursively问题

答案 4 :(得分:-1)

静态方法是永久性的,它不是存储在堆栈中,而是存储在堆中。您编写的代码只是从堆中反复调用相同的代码,因此它不会抛出StackOverFlowError。此外,System.out.println("main");内的字符串存储在永久性的同一位置。即使您反复调用代码,也会使用相同的字符串对象,但它不会填充堆栈。

我从以下链接得到了这个解释:

http://www.oracle.com/technetwork/java/javase/memleaks-137499.html#gbyuu

  

3.1.2详细消息:PermGen空间详细消息PermGen空间表示永久生成已满。永久的   generation是类和方法对象所在的堆区域   存储。如果应用程序加载了大量的类,那么   可能需要使用增加永久代的大小   -XX:MaxPermSize选项。

     

Interned java.lang.String对象也存储在permanent中   代。 java.lang.String类维护一个字符串池。   调用实习方法时,该方法会检查池以查看   如果池中已存在相等的字符串。如果有,那么   实习方法返回它;否则它将字符串添加到池中。在   更精确的术语,java.lang.String.intern方法用于   获取字符串的规范表示;结果是   引用将返回的同一个类实例   字符串作为文字出现。如果一个应用程序实习生人数很多   字符串,永久世代可能需要增加   它的默认设置。

     

发生此类错误时,文本String.intern或   ClassLoader.defineClass可能出现在堆栈跟踪的顶部附近   那是印刷的。

     

jmap -permgen命令打印对象中的对象的统计信息   永久生成,包括有关内化字符串的信息   实例