据我所知,这段代码应该抛出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);
}
}
}
答案 0 :(得分:5)
Error不是Exception
。因此捕获任何异常都不会捕获StackOverflowError。
所以让我们先解决“明显的错误” - (这段代码在本回答的后面部分是不可取的):
catch(Throwable e){
System.out.println("Catch");
}
如果进行此更改,您会发现代码仍无法打印。但它没有打印出一个非常不同的原因......
强烈建议不要抓住任何ERROR
(包括StackOverflowError
)。但是在这里你不仅要抓住一个,而是抓住一个,因为它发生在堆栈顶部。即使使用您的代码(没有上述更改),finally
块也会有效地捕获错误。
当堆栈已满并且您尝试向其添加更多内容时,会发生StackOverflowError
。因此,当您捕获错误时,堆栈仍然已满。您无法调用任何方法(甚至打印到控制台),因为堆栈已满。因此,在成功打印之前,StackOverflowError
会抛出第二个catch
。
结果就是:
finally
,因为最终始终调用。main
这里的关键是最终它将开始打印一些东西。但是对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命令打印对象中的对象的统计信息 永久生成,包括有关内化字符串的信息 实例