理解Java堆栈

时间:2013-02-26 07:07:54

标签: java exception-handling stack-overflow buffered

有这段代码:

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”八次而不再打印。当遇到println()时,Java如何调用StackOverflowError

7 个答案:

答案 0 :(得分:13)

此处您正在抓取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 reference to buffered streams

答案 1 :(得分:5)

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

JLS说:

  

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

     

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

     

允许上述延迟允许优化代码进行检测   并将这些异常抛到可以处理的地方   他们在遵守Java编程语言的语义的同时。一个   简单的实现可能会轮询异步异常   每个控制转移指令的点。由于程序有一个   有限大小,这提供了检测一个总延迟的界限   异步异常。

答案 2 :(得分:3)

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

答案 3 :(得分:1)

有一点很清楚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

答案 4 :(得分:0)

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

通过以下代码证明:

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()块的次数未定义。

答案 5 :(得分:0)

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

答案 6 :(得分:-2)

堆栈溢出。

您只是在异常时打印,同时程序会进行溢出。

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

该计划的目的是什么?