理解Java堆栈

有这个代码:

public class Main { public static void main(final String[] args) throws Exception { System.out.print("1"); doAnything(); System.out.println("2"); } private static void doAnything() { try { doAnything(); } catch (final Error e) { System.out.print("y"); } } } 

还有输出:

 1yyyyyyyy2 

为什么它打印“y”八次而不再打印。 遇到StackOverflowError时,Java如何调用println()

在这里你捕获Error而不是Exception在这种情况下你的程序会崩溃。

如果您尝试此代码(修改为添加静态计数器)

 public class StackError { static int i = 1; public static void main(final String[] args) throws Exception { System.out.print("1"); doAnything(); System.out.println("2"); } private static void doAnything() { try { i++; // System.out.println(i); doAnything(); } catch (Error e) { System.out.print("y"+i+"-"); } } } 

产量

  1y6869-2 

因此,它有stackerror 6869次(不同运行的更改),并打印最后一个值。 如果您只是像之前那样打印y ,那么输出可能会被缓冲并且不会被刷新,因为它不是println


更新

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

回到这个场景,它取决于堆栈填充多少的内部动态以及doAnything() catch的执行数量,并且这些字符数被写入缓冲区。 在主背部,它最终印有2

javadoc引用缓冲流

我敢打赌,通过在catch块中调用print ,可以强制执行外部块捕获的另一个 StackOverflowError 。 其中一些调用没有足够的堆栈来实际写入输出流。

JLS说:

请注意,由于本机方法执行或Java虚拟机资源限制,可能会通过方法调用以及异步方式同步抛出StackOverflowError。

Java SE平台允许在抛出异步exception之前进行少量但有限的执行。

上面提到的延迟允许优化代码在遵循Java编程语言的语义的同时检测并抛出这些exception。 一个简单的实现可能会在每个控制传输指令的点处轮询异步exception。 由于程序具有有限的大小,因此这提供了检测异步exception的总延迟的界限。

第一次发生StackOverFlowError时,将取消对最后一个doAnything()的调用,并将控件从最后一个doAnything()返回到catch块。

但是,因为堆栈仍然几乎已满,调用System.out.print("y")的简单事实将导致另一个StackOverflowError因为需要在堆栈上推送一些值然后调用函数print()

因此,再次发生另一个StackOverflowError ,现在返回上一个doAnything()的catch {}块返回; 其中会发生另一个StackOverflowError ,因为对System.out.println("y")执行单个调用所需的堆栈空间大于从doAnything()返回调用所释放的空间量。

只有当堆栈上有足够的空间来执行对System.out.print("y")的调用时,此进程才会停止并且catch块将成功完成。 我们可以通过运行以下代码看到:

 public class Principal3b { static int a = 0; static int i = 0; static int j = 0; public static void main(String[] args) { System.out.println("X"); doAnything(); System.out.println("Y"); System.out.println(i); System.out.println(j); } private static void doAnything() { a++; int b = a; try { doAnything(); } catch (final Error e) { i++; System.out.println(a); System.out.println(b); j++; } } } 

注意,使用println(a)代替print(a) ; 因此,如果一切运行正常,则应在a的每个值之后打印一个新行。

但是,当我运行它时,我得到以下结果:

 X 62066206620662066206620662066206 6190 Y 17 1 

这意味着已经有17次尝试运行catch块。 在这些catch块执行中,9个在生成StackOverflowError之前无法打印任何内容。 7能够打印6190的值,但是在它自己再次出现错误之前无法打印换行符,最后,有一个能够打印6190的值和后面的换行符; 因此最终允许其catch块在没有任何新的StackOverflowError的情况下完成并且优雅地返回调用堆栈。

当我们处理StackOverflowError时,这些数字只是一个例子,不仅在机器之间而且在执行之间变化很大,添加或删除任何类型的指令的简单事实也应该改变这些值。 但是,这里看到的模式应该保持不变。

有一点很清楚System.out.print(“y”); 在catch中创造了这个难题。 如果我们将代码更改为

 static int n; public static void main(final String[] args) throws Exception { System.out.println("1"); doAnything(); System.out.println(n); } private static void doAnything() { try { doAnything(); } catch (Error e) { n++; } } 

它打印

 1 1 

好吧,没有。 堆栈溢出错误被触发的次数是未定义的。 但是,JVM允许您从StackOverflowError错误中恢复并正常继续执行系统。

通过以下代码certificate:

 public class Really { public static void main(final String[] args) throws Exception { System.out.print("1"); doAnything(); System.out.println("2"); } private static void doAnything() { try { throw new StackOverflowError(); //doAnything(); } catch(final Error e){ System.out.print("y"); } } } 

但请注意,正如@Javier所说,JVM同步或异步抛出StackOverflowError (这意味着它可能被另一个线程抛出,可能是本机线程),这就是为什么不可能获得错误的堆栈跟踪的原因。 没有。 线程中遇到catch()块的次数是未定义的。

此外, Error类型的对象不是Exceptions对象,它们代表exception条件。 Errors表示不是由程序错误引起的exception情况,通常在程序执行期间通常不会发生,例如JVM内存不足。 虽然它们共享一个共同的超类Throwable ,意味着两者都可以抛出,但它可以放在一个catch但通常不应该被捕获,因为它们代表罕见的,难以处理的exception条件。

堆栈溢出。

您只是在exception时打印,同时程序会进入溢出状态。

此时发生的情况取决于个别系统,内存等。

该计划的目的是什么?