在try-catch中混淆来自无限recursion的输出
考虑下面的代码。
public class Action { private static int i=1; public static void main(String[] args) { try{ System.out.println(i); i++; main(args); }catch (StackOverflowError e){ System.out.println(i); i++; main(args); } } }
我得到了我正确的价值达4338
。 捕获StackOverflowError
输出获取连线后,如下所示。
4336 4337 4338 // up to this point out put can understand 433943394339 // 4339 repeating thrice 434043404340 4341 434243424342 434343434343 4344 4345 434643464346 434743474347 4348 434943494349 435043504350
在这里考虑现场演示。 它工作正常,直到i=4330
。 其实这是怎么发生的?
供参考:
我做了下面的代码,以了解这里发生了什么。
public class Action { private static int i = 1; private static BufferedWriter bw; static { try { bw = new BufferedWriter(new FileWriter("D:\\sample.txt")); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { bw.append(String.valueOf(i)+" "); try { i++; main(args); } catch (StackOverflowError e) { bw.append(String.valueOf(i)+" "); i++; main(args); } } }
现在以前的问题不在那里。 现在i
价值16824744
正确,并进一步运行。 我正在跳,这可能会跑到i=2,147,483,647
(int的最大值)的值没有问题。
println()
有一些问题。 下面也有类似的答案。 但为什么?
什么将是实际的原因?
请注意433943394339
没有换行符。 这表明在System.out.println()
内发生了错误。
这里的要点是System.out.println()
需要一些堆栈空间才能工作,所以从System.out.println()
抛出StackOverflowError
。
这里是你的代码与标记点:
public static void main(String[] args) { try{ System.out.println(i); // (1) i++; main(args); // (2) }catch (StackOverflowError e){ System.out.println(i); // (3) i++; main(args); // (4) } }
让我们来想象一下当我= 4338
时recursion级别N发生了什么:
- 声明(1)在级别N打印
4338
。 输出4338\n
-
i
增加到4339
- (2)控制stream程进入N + 1层,
- 在级别N + 1语句(1)试图打印
4339
,但System.out.println()
将引发一个StackOverflowError
它打印换行符之前。 输出4339
-
StackOverflowError
在级别N + 1被捕获,语句(3)尝试打印4339
并再次出于同样的原因失败。 输出4339
- 在N级捕获exception。此时有更多的可用堆栈空间,因此语句(3)尝试打印
4339
并成功(换行符被正确打印)。 输出4339\n
- (4)中,
i
递增并且控制stream再次进入N + 1层,
在这之后,情况重复4340
。
我不确定为什么有些数字在没有换行符的序列之间打印相关,也许它与System.out.println()
内部工作和它使用的缓冲区有关。
我怀疑发生的是这样的:
- 打印我
- 打印换行符
- 增加i
- inputmain
- 打印我
- 打印换行符
- 增加i
- inputmain
- 打印我
- 抛出StackOverflow(而不是打印换行符)
- 回到主线,现在在捕捉
- 打印我
- StackOverflow再次抛出(而不是打印换行符)
- 返回到主体,在另一个捕获体。
- 打印我
- 打印换行符(现在不再失败,因为我们是两个更高的级别)
- inputmain,然后回到1。
根据我的testing:
当通过尝试块抛出exception时, i
有相同的值,当它在catch块(因为它不增加,由于例外)
然后在catch块里面抛出同样的exception,这又被catch块捕获了!
我试过以下代码
try { System.out.println("Try " + i); i++; main(args); } catch (StackOverflowError e) { System.out.println("\nBefore"); System.out.println("Catch " + i); i++; System.out.println("After"); main(args); }
输出:
Try 28343 Try 28344 Before Before Before Before Catch 28344 After Try 28345 Try 28346 Try 28347 Try 28348 Before Before Before Before Catch 28348 After Try 28349
当try块引发exception时,它被catch块捕获,但是当它进入System.out.println("Catch " + i);
再次exception抛出4次(在我的月食)没有打印System.out.println("Catch " + i);
在上面的输出中,我已经通过打印“before”之前打印了四次System.out.println("Catch " + i);
文本进行了testingSystem.out.println("Catch " + i);
如果println
(或者它所调用的某个方法)的执行导致堆栈溢出,那么将会从封装的main
化身的catch子句中打印相同的i
值。
确切的行为是相当不可预知的,因为它依赖于仍然可用的堆栈空间。
由于其他答案已经解释它必须做System.out.println需要额外的空间在堆栈上,从而引发自己StackOverflowError。
试试这里的代码来看看一些不同的行为,这表明在某些时候,在这个地方抛出了exception,这样i ++就不会再发生了。
public class Action { private static int i = 1; private static StringBuffer buffer = new StringBuffer(); public static void main(String[] args) { try { print(); main(args); } catch (StackOverflowError e) { print(); main(args); } } private static void print() { buffer.append(i).append("\n"); i++; if (i % 1000 == 0) { System.out.println("more: " + buffer); buffer = new StringBuffer(); } } }
要学习的重点是:绝对不要错误地指出你的JVM有什么严重的问题,而这些问题是不能正常处理的。
底线是StackOverflowError
是一个错误,而不是一个例外。 你正在处理一个错误,而不是一个exception。所以当程序进入catch块的时候已经崩溃了。关于这个程序的奇怪行为,下面是这个官方文档中对java缓冲区的解释:
例如,一个autoflush PrintWriter对象在每次调用println或format时刷新缓冲区
System.out.println()
内部调用缓冲的PrintStream
。 你不会从缓冲区中丢失任何数据,它会在填满之后被写入输出(在你的情况下是terminal),或者当你明确地调用flush时。
回到这个场景,它取决于堆栈填充的内部dynamic以及在main()
的catch中能够执行多less个打印语句,并且这些字符被写入缓冲区。 在执行第一次尝试之后,即在第一次发生堆栈溢出时,第一个System.out.println()无法打印新行,因此它将剩余的字符刷新到缓冲区。
axtavt答案是非常完整的,但我想补充一点:
正如你可能知道堆栈被用来存储variables的内存,当你达到限制时你不能创build新的variables,确实System.out.println将需要一些堆栈资源
787 public void More ...println(Object x) { 788 String s = String.valueOf(x); 789 synchronized (this) { 790 print(s); 791 newLine(); 792 } 793 }
然后在打印之后,错误不允许你甚至调用newLine,它在打印上再次打破。 基于这一点,你可以通过改变你的代码来确保是这样的:
public class Action { static int i = 1; public static void main(String[] args) { try { System.out.print(i + "\n"); i++; main(args); } catch (StackOverflowError e) { System.out.print(i + " SO " + "\n"); i++; main(args); } } }
现在,您不会要求堆栈处理新行,您将使用常量“\ n”,并且可以向exception打印行添加一些debugging,并且您的输出在同一行中不会有多个值:
10553 10553 SO 10553 SO 10554 10554 SO 10554 SO 10555 10556 10557 10558
它会保持中断,直到获得一些资源来分配新的数据并传递给下一个i值。