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

时间:2013-07-24 13:55:39

标签: java recursion jvm stack-overflow try-finally

我有一个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 个答案:

答案 0 :(得分: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没有做任何特别的事情。 “有效无限循环”行为完全完全到你的程序编写方式。

  

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

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

答案 1 :(得分:2)

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

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

参见“11.1.1。异常的种类”: http://docs.oracle.com/javase/specs/jls/se7/html/jls-11.html

答案 2 :(得分:2)

假设程序正在执行asd()方法,堆栈空间即将结束。还假设该方法不是“内部尝试”和“最终内部”,而是打印一个计数器,告诉您堆栈的距离:

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

重要的是要注意:

  1. 即使在StackOverflowError
  2. 的情况下,也会执行finally块
  3. 遇到StackOverflowError的程序可能处于不可预测的状态。这里唯一可观察到的效果是println不输出换行符。在一个更复杂的程序中,这可能意味着你不能相信程序正在进行的任何事情的状态,并且最安全的做法是完全拯救。

答案 3 :(得分:1)

当您在finally块中调用asd时,您将收到相同的错误 - 您需要让堆栈上的asd帧返回/弹出以解决错误

答案 4 :(得分:1)

  

但是finally块中的递归函数是如何执行的呢?   因为我们已经面临StackOverflowError?

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

已检查的例外情况

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

未经检查的例外

java.lang.Throwable班级陈述......

  

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

由于StackOverflowErrorError的子类,因此不会将其视为已检查的异常,因此“未选中”。 由于编程错误,通常会出现未经检查的异常,例如:在您的情况下,无限递归的错误终止。如果您尝试访问数组中的空变量或无效索引的某些成员,则可能会出现其他未经检查的异常。这些异常几乎不可能在编译时检查,并且更常用于向程序员显示他或她的代码中的错误。

进一步阅读

Java Exceptions

Checked vs Unchecked Exceptions