首页 文章

finally块中的堆栈溢出错误处理

提问于
浏览
5

我有一个java程序,运行无限次 .

程序代码:

void asd()
{
    try
    {
        //inside try block
        System.out.println("Inside try !!!");
        asd();
    }
    finally
    {
        //inside finally
        System.out.println("Inside finally !!!");
        asd();
    }
}

输出:通过不断打印两个系统,该程序无限运行 .

我的问题:在某些时候,它开始从try块中抛出StackOverflowErrors,因此它到达finally块,我们再次以递归方式调用此函数 . 但是,当我们已经面临StackOverflowError时,finally块中的递归函数如何执行?

JVM如何处理这种情况?如果我们也得到OutOfMemoryErrors会发生同样的行为吗?

5 回答

  • 4

    问题是你的示例程序是病态的 . 它不起作用,它无法工作 .

    但是,当我们已经面对StackOverflowError时,finally块中的递归函数如何执行 .

    有一个相当复杂的调用序列正在进行 . 让我们假设堆栈可以容纳3帧 . “手动执行”为我们提供了一系列调用/调用堆栈快照,如下所示:

    asd()
    asd() > try
    asd() > try > asd() 
    asd() > try > asd() > try 
    asd() > try > asd() > try > asd() // Stack Overflow!
    asd() > try > asd() > finally
    asd() > try > asd() > finally > asd() // Stack Overflow!
    asd() > finally
    asd() > finally > asd()
    asd() > finally > asd() > try
    asd() > finally > asd() > try > asd() // Stack Overflow!
    asd() > finally > asd() > finally
    asd() > finally > asd() > finally > asd() // Stack Overflow!
    
    END
    

    正如你所看到的深度为3的堆栈,我们进行了7次调用,其中4次失败,堆栈溢出 . 如果对深度为4的堆栈执行手动执行,则将获得15次调用,5 => 31.模式为 N => 2**N - 1 calls .

    在您的情况下,默认堆栈将能够容纳数百甚至数千个递归调用 .

    说N = 100. 2 ** 100是非常大量的呼叫 . 它不是无限的,但在程序终止之前你可能已经死了 .

    JVM如何处理这种情况?

    如上 . JVM没有做任何特别的事情 . "effectively infinite loop"行为完全取决于程序的编写方式 .

    如果我们也得到OutOfMemoryErrors,会发生同样的行为吗?

    呃......这取决于你的计划 . 但我相信你可以编写一个展示类似行为模式的示例程序 .

  • 1

    假设程序正在执行 asd() 方法,堆栈空间即将结束 . 还假设该方法不是"inside try"和"inside finally"而是打印一个计数器,告诉您堆栈的距离:

    void asd(int i){
        try{
            //inside try block
            System.out.print(i);
            System.out.println("t");
            asd(i+1);
        }
        finally{
            //inside finally
            System.out.print(i);
            System.out.println("f");
            asd(i+1);
        }
    }
    

    }

    现在,当 i 为9154时,这就是当程序即将耗尽堆栈空间时所执行的操作 .

    println("t") 的调用输出字符,然后继续调用println()方法 . 这使程序耗尽堆栈空间,因此执行将移至 finally 块 . 打印新行时,再次调用println会耗尽堆栈空间 . 再次抛出错误,执行转到当前方法调用上方的激活框架中的 finally . 这使程序第二次打印 f ,并且由于我们从堆栈中弹出一个激活帧,此调用现在正常完成,并打印出一个新行 . 到目前为止,该程序给出了这样的输出:

    ...
    9152t
    9153t
    9154t9154f9153f              // notice we jumped to i=1953
    

    现在,该方法再次调用自身,现在从finally块调用 . 堆栈空间的情况就像之前一样,所以程序执行如上,除了因为我们在i = 1953的方法调用的finally块中,程序执行结束于方法调用的finally块我= 1952年:

    9154t9154f9152f
    

    i = 9152的finally块再次调用 asd ,传递i = 9153,并且从现在开始有足够的堆栈空间来打印方法从try块输出的完整行:

    9153t
    

    然后继续调用自己,在这个调用中将最终再次耗尽堆栈空间,给出输出:

    9154t9154f9153f
    

    ......其余的输出可以用类似的方式解释:

    9154t9154f9151f
    9152t
    9153t
    9154t9154f9153f
    9154t9154f9152f
    9153t
    9154t9154f9153f
    9154t9154f9150f
    9151t
    9152t
    9153t
    ...
    

    重要的是要注意:

    • 即使在 StackOverflowError 的情况下也会执行finally块 .

    • 遇到 StackOverflowError 的程序可能处于不可预测的状态 . 这里唯一可观察到的影响是 println 不信任该程序正在进行的任何事情的状态,并且最安全的做法是完全拯救 .

  • 1

    不打算处理错误,即OutOfMemoryError,StackOverflowError等 . 它们使JVM处于未定义状态,没有任何保证 . 此时,您的应用程序必须简单地终止,您必须解决导致此问题的问题 .

    您的应用程序不应尝试处理错误或在发生错误后运行 . 如果您此时正在调用递归函数,那么您应该受到责备 . 结果是不可预测的 .

    见"11.1.1. The Kinds of Exceptions":http://docs.oracle.com/javase/specs/jls/se7/html/jls-11.html

  • 2

    你会得到同样的在finally块中调用 asd 时出错 - 您需要让堆栈上的 asd 帧返回/弹出以解决错误

  • 2

    但是,当我们已经面临StackOverflowError时,finally块中的递归函数是如何执行的?

    如果您尝试处理 StackOverflowError ,它将只会导致更多的后续StackOverflowErrors,因为您正在尝试在已经完整的堆栈上执行进一步的执行 . 您不应该尝试捕获,使用此信息来更好地构建代码 . 这是java中未经检查的异常的要点 . 如果您不熟悉不同类型的例外......

    已检查的例外情况

    检查异常是在编译时检查异常并表示需要处理的条件(通过try-catch语句),因为它超出了程序的控制范围 . 例如,如果要在给定线程内的任何位置调用 Thread.sleep() ,则需要处理在线程被另一个线程中断时可能发生的潜在 InterruptedException . 由于这种情况的可能性在编译时是已知的,因此您需要程序员来处理这种情况 .

    未经检查的例外情况

    java.lang.Throwable班说......

    出于编译时检查异常的目的,Throwable和Throwable的任何子类(也不是RuntimeException或Error的子类)都被视为已检查的异常 .

    由于 StackOverflowErrorError 的子类,因此不会将其视为已检查的异常,因此"unchecked" . Unchecked exceptions often arise due to programming errors, e.g. in your case, the incorrect termination of an infinite recursion . 如果您尝试访问数组中的空变量或无效索引的某些成员,则可能会出现其他未经检查的异常 . 这些异常几乎不可能在编译时检查,并且更常用于向程序员显示他或她的代码中的错误 .

    Further Reading

    Java Exceptions

    Checked vs Unchecked Exceptions

相关问题