53

考虑以下代码。

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()。下面也有类似的答案。但为什么?

真正的原因会是什么?

4

7 回答 7

52

请注意433943394339. 它表明内部发生了错误System.out.println()

这里的要点是System.out.println()需要一些堆栈空间才能工作,因此StackOverflowErrorSystem.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 = 时在递归的第 N 级会发生什么4338

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

在此之后,情况以 重复4340

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

于 2013-08-19T10:50:04.730 回答
17

我怀疑正在发生的是:

  1. 打印我
  2. 打印换行符
  3. 增加我
  4. 输入主
  5. 打印我
  6. 打印换行符
  7. 增加我
  8. 输入主
  9. 打印我
  10. StackOverflow 被抛出(而不是打印换行符)
  11. 回到主线,现在在赶上
  12. 打印我
  13. StackOverflow 再次被抛出(而不是打印换行符)
  14. 返回 main,在另一个 catch 体中。
  15. 打印我
  16. 打印换行符(现在不再失败,因为我们高了两个级别)
  17. 输入 main,然后返回 1。
于 2013-08-19T10:51:03.423 回答
4

根据我的测试:

当 try 块抛出异常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);

于 2013-08-19T11:01:23.723 回答
3

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

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

于 2013-08-19T10:50:08.707 回答
1

The bottom line is that StackOverflowError is an error, not an exception. You are handling an error, not an exception.So the program has already crashed when it enters the catch block.Regarding the strange behaviour of the program, below is the explanation on basis of java buffers from this official doc:

For example, an autoflush PrintWriter object flushes the buffer on every invocation of println or format

The System.out.println() internally calls the PrintStream which is buffered. You don't loose any data from the buffer, it gets all written to the output( terminal in your case) after it fills up, or when you explicitly call flush on it.

Coming back to this scenario, it depends on the internal dynamics of how much the stack is filled up and how many print statements were able to get executed from the catch in main() and those number of characters were written to the buffer. Here after the first try is executed, i.e. in the event of first occurence of the stack overflow, the first System.out.println() fails to print the new line so it flushed the buffer with remaining characters.

于 2013-08-19T11:08:37.887 回答
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 存在无法正常处理的严重错误。

于 2013-08-19T10:54:02.860 回答
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     }

然后在调用 print 之后,错误甚至不允许你调用 newLine,它在 print 上再次中断。基于此,您可以通过如下更改代码来确保是这种情况:

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 值。

于 2013-09-09T19:10:56.797 回答