在try-catch中混淆无限递归的输出

时间:2013-08-19 10:15:07

标签: java stack-overflow

请考虑以下代码。

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

在此处考虑Live演示。它正常工作到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()存在一些问题。下面也有类似的答案。但为什么?

实际原因是什么?

7 个答案:

答案 0 :(得分:52)

请注意433943394339中缺少换行符。它表明在System.out.println()内发生了错误。

这里的要点是System.out.println()需要一些堆栈空间才能工作,因此StackOverflowError会抛出System.out.println()

以下是带有标记点的代码:

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)
    }
}

让我们假设当i = 4338时,在递归的N级发生了什么:

  • N级声明(1)打印4338输出4338\n
  • i会增加到4339
  • 控制流程在(2)
  • 进入N + 1级
  • 语句(1)在级别N + 1尝试打印4339,但System.out.println()在打印换行符之前会抛出StackOverflowError输出4339
  • StackOverflowError在级别N + 1处被捕获,语句(3)尝试打印4339并且由于同样的原因再次失败。 输出4339
  • 在N级捕获异常。此时有更多可用的堆栈空间,因此statement(3)尝试打印4339并成功(正确打印换行符)。 输出4339\n
  • i递增,控制流程再次在(4)
  • 进入等级N + 1

在此之后,情况会以4340重复。

我不确定为什么有些数字在没有换行符的序列之间打印出来,可能与System.out.println()的内部工作和它使用的缓冲区有关。

答案 1 :(得分:17)

我怀疑正在发生的是:

  1. 打印我
  2. 打印换行符
  3. 增加我
  4. 输入主要
  5. 打印我
  6. 打印换行符
  7. 增加我
  8. 输入主要
  9. 打印我
  10. 抛出StackOverflow(而不是打印换行符)
  11. 返回主要,现在在捕获
  12. 打印我
  13. 再次抛出StackOverflow(而不是打印换行符)
  14. 返回主处,在另一个捕获体内。
  15. 打印我
  16. 打印换行符(现在不再失败,因为我们高出两级)
  17. 输入main,然后返回1.

答案 2 :(得分:4)

根据我的测试:

当try Block抛出Exception时,i在catch块中具有相同的值(因为它没有因异常而增加)

然后在catch块中抛出相同的异常,并且它再次被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块抛出异常时,它被catch块捕获但当它再次转到System.out.println("Catch " + i);异常被抛出4次(在我的eclipse中)没有打印System.out.println("Catch " + i);

与上面的输出一样,我通过打印“之前”文本进行了测试,在打印之前打印了四次System.out.println("Catch " + i);

答案 3 :(得分:3)

如果执行println(或其中一个方法)导致堆栈溢出,您将从封闭i的catch子句中打印相同的main值化身。

确切的行为是不可预测的,因为它取决于仍然可用的堆栈空间。

答案 4 :(得分:1)

正如其他答案已经解释的那样,它与System.out.println有关,需要在堆栈上留出额外空间,从而使自己陷入StackOverflowError。

在这里尝试使用此代码来查看一些不同的行为,这些行为表明在某些时候会有异常被抛出,以至于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存在严重错误,无法正常处理。

答案 5 :(得分:1)

底线是StackOverflowError是错误,不是例外。你正在处理一个错误,而不是异常。所以程序在进入catch块时已经崩溃。关于程序的奇怪行为,下面是基于来自this官方doc的java缓冲区的解释:< / p>

  

例如,autoflush PrintWriter对象会刷新缓冲区   每次调用println或format

System.out.println()在内部调用缓冲的PrintStream。您不会从缓冲区中丢失任何数据,它会在填满之后或在您明确调用flush时将所有数据写入输出(在您的情况下为终端)。

回到这个场景,它取决于填充堆栈的内部动态以及能够从main()中的catch捕获执行的打印语句数量,并且这些字符数被写入缓冲区。在执行第一次尝试之后,即在第一次出现堆栈溢出的情况下,第一个System.out.println()无法打印新行,因此它用剩余的字符刷新缓冲区。

答案 6 :(得分:0)

axtavt答案非常完整,但我想补充一点:

您可能知道堆栈用于存储变量的内存,基于此,当您达到限制时无法创建新变量,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”,你可以在异常打印行中添加一些调试,并且你的输出在同一行中没有多个值: / p>

10553
10553 SO
10553 SO
10554
10554 SO
10554 SO
10555
10556
10557
10558

它会一直被破坏,直到获得一些资源来分配新数据并传递给下一个i值。