请考虑以下代码。
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()
的内部工作及其使用的缓冲区有关。
我怀疑正在发生的是:
- 打印i
- 打印换行符
- 增加i
- 输入main
- 打印i
- 打印换行符
- 增加i
- 输入main
- 打印i
- StackOverflow被抛出(而不是打印换行符)
- 返回主,现在处于捕获状态
- 打印i
- StackOverflow再次被抛出(而不是打印换行符)
- 返回主,在另一个捕获体中
- 打印i
- 打印换行符(现在不再失败,因为我们高了两级)
- 输入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时,它会被全部写入输出(在你的情况下是终端)。
回到这个场景,这取决于堆栈被填充了多少,以及有多少打印语句能够从i
0中的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值。