理解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时打印,同时程序会进入溢出状态。
此时发生的情况取决于个别系统,内存等。
该计划的目的是什么?
- 在Java中处理RuntimeExceptions
- 为什么用户定义的exception类在java中是首选/重要的?
- 不能使用try / catch块处理java未经检查的exception?
- 如果服务器返回错误状态,java.net.HttpUrlConnection是否总是抛出IOException?
- Javaexception处理 – 捕获超类exception
- exception处理尝试捕获内部catch
- Swing Worker中的优雅exception处理
- 使用Strutsvalidation框架进行error handling的问题
- 如何使用Spring Data Rest和PagingAndSortingRepository处理exception?