在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()内部工作和它使用的缓冲区有关。

我怀疑发生的是这样的:

  1. 打印我
  2. 打印换行符
  3. 增加i
  4. inputmain
  5. 打印我
  6. 打印换行符
  7. 增加i
  8. inputmain
  9. 打印我
  10. 抛出StackOverflow(而不是打印换行符)
  11. 回到主线,现在在捕捉
  12. 打印我
  13. StackOverflow再次抛出(而不是打印换行符)
  14. 返回到主体,在另一个捕获体。
  15. 打印我
  16. 打印换行符(现在不再失败,因为我们是两个更高的级别)
  17. 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值。