我有一个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会发生同样的行为吗?
答案 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
...
重要的是要注意:
StackOverflowError
。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被视为已检查的异常。
由于StackOverflowError
是Error
的子类,因此不会将其视为已检查的异常,因此“未选中”。 由于编程错误,通常会出现未经检查的异常,例如:在您的情况下,无限递归的错误终止。如果您尝试访问数组中的空变量或无效索引的某些成员,则可能会出现其他未经检查的异常。这些异常几乎不可能在编译时检查,并且更常用于向程序员显示他或她的代码中的错误。
进一步阅读