finally块中的堆栈溢出error handling

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

程序代码:

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

OUTPUT:通过不断打印两个sysout,这个程序无限运行。

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

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

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

但是,当我们已经面对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会发生同样的行为吗?

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

假设程序正在执行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再次调用asd ,传递i = 9153,从现在开始有足够的堆栈空间来打印一个完整的行,方法从try-block输出:

 9153t 

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

 9154t9154f9153f 

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

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

重要的是要注意:

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

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

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

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

在finally块中调用asd时会出现相同的错误 – 需要让堆栈上的asd帧返回/弹出以解决错误

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

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

已检查的例外情况

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

未经检查的例外情况

java.lang.Throwable类声明了……

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

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

进一步阅读

Java例外

已检查与未经检查的例外情况