try-catch中的无限递归输出令人困惑



请考虑以下代码。

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

现在上一期不在了。现在CCD_ 4到CCD_。我正在跳跃,这可能会运行到i=2,147,483,647的值(int的最大值),而不会出现问题。

println()存在一些问题。下面也有类似的答案。但为什么呢?

实际原因是什么?

注意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输出4338n
  • i递增为4339
  • 控制流在(2)处进入N+1级
  • N+1级的语句(1)试图打印4339,但System.out.println()在打印新行之前抛出一个StackOverflowError输出4339
  • StackOverflowError在N+1级被捕获,语句(3)尝试打印4339,但由于同样的原因再次失败输出4339
  • 在N级捕获到异常。此时有更多的堆栈空间可用,因此语句(3)尝试打印4339并成功(换行正确打印)输出4339n
  • i递增,并且控制流在(4)处再次进入级别N+1

在这一点之后,情况以4340重复。

我不知道为什么有些数字在没有换行的序列之间正确打印,也许这与System.out.println()的内部工作及其使用的缓冲区有关。

我怀疑正在发生的是:

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

根据我的测试:

当try块抛出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); 之前打印四次的文本

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

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

正如其他答案已经解释的那样,它与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出现了无法正常处理的严重问题。

底线是StackOverflowError是一个错误,而不是异常。您正在处理一个错误,而不是一个异常。因此,当程序进入catch块时,它已经崩溃了。关于程序的奇怪行为,以下是基于该官方文档中java缓冲区的解释:

例如,自动刷新PrintWriter对象会刷新上的缓冲区每次调用println或格式化

CCD_ 38在内部调用被缓冲的CCD_。你不会从缓冲区中丢失任何数据,在缓冲区填满后,或者当你显式调用flush时,它会被全部写入输出(在你的情况下是终端)。

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

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     }

然后在调用打印后,错误甚至不允许您调用换行符,它在打印时再次中断。基于此,你可以通过这样更改你的代码来确保这一点:

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",您可以对异常打印行添加一些调试,并且您的输出不会在同一行中有多个值:

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

它将一直中断,直到获得一些资源来分配新数据并传递给下一个i值。

最新更新