5

我有一个java程序,可以无限次运行。

程序代码:

void asd()
{
    try
    {
        //inside try block
        System.out.println("Inside try !!!");
        asd();
    }
    finally
    {
        //inside finally
        System.out.println("Inside finally !!!");
        asd();
    }
}

输出:这个程序通过不断地打印两个系统输出来无限运行。

我的问题:在某些时候,它开始从 try 块中抛出 StackOverflowErrors ,因此它到达 finally 块,我们再次递归地调用这个函数。但是由于我们已经面临 StackOverflowError,finally 块中的递归函数是如何执行的呢?

JVM 如何处理这种情况?如果我们也得到 OutOfMemoryErrors 会发生同样的行为吗?

4

5 回答 5

4

问题是您的示例程序是病态的。它不会起作用,也不会起作用。

但是,finally 块中的递归函数是如何执行的,因为我们已经面临StackOverflowError.?

有一个相当复杂的调用序列正在进行。让我们假设堆栈可以容纳 3 帧。“手动执行”为我们提供了一系列调用/调用堆栈快照,如下所示:

asd()
asd() > try
asd() > try > asd() 
asd() > try > asd() > try 
asd() > try > asd() > try > asd() // Stack Overflow!
asd() > try > asd() > finally
asd() > try > asd() > finally > asd() // Stack Overflow!
asd() > finally
asd() > finally > asd()
asd() > finally > asd() > try
asd() > finally > asd() > try > asd() // Stack Overflow!
asd() > finally > asd() > finally
asd() > finally > asd() > finally > asd() // Stack Overflow!

END

如您所见,堆栈深度为 3,我们进行了 7 次调用,其中 4 次因堆栈溢出而失败。如果您对深度为 4 的堆栈执行手动执行,您将获得 15 次调用,5 => 31。模式是N => 2**N - 1 calls.

在您的情况下,默认堆栈将能够容纳数百甚至数千个递归调用。

假设 N = 100。2**100 是一个非常大的调用数。它不是无限的,但在程序终止之前你可能已经死了。

JVM 如何处理这种情况?

如上。JVM 没有做任何特别的事情。“有效的无限循环”行为完全取决于程序的编写方式。

OutOfMemoryError如果我们也得到s会发生同样的行为吗?

呃......这取决于你的程序。但我敢肯定,您可以编写一个表现出类似行为模式的示例程序。

于 2013-07-24T14:32:20.200 回答
2

不打算处理错误,即 OutOfMemoryError、StackOverflowError 等。它们使 JVM 处于未定义状态,没有任何保证。此时,您的应用程序必须简单地终止,并且您必须解决导致此问题的问题。

您的应用程序不应尝试处理错误或在发生错误后运行。如果此时您正在调用递归函数,那么您就是罪魁祸首。结果是不可预测的。

请参阅“11.1.1. 异常类型”: http ://docs.oracle.com/javase/specs/jls/se7/html/jls-11.html

于 2013-07-24T14:03:41.770 回答
2

假设程序正在执行该asd()方法并且堆栈空间即将结束。还假设该方法不是“内部尝试”和“最终内部”,而是打印一个计数器,告诉您堆栈的距离:

void asd(int i){
    try{
        //inside try block
        System.out.print(i);
        System.out.println("t");
        asd(i+1);
    }
    finally{
        //inside finally
        System.out.print(i);
        System.out.println("f");
        asd(i+1);
    }
}

}

现在这就是程序在堆栈空间即将用完时所做的事情,i即 9154 时。

调用println("t")输出字符,然后继续调用 println() 方法。这会使程序用尽堆栈空间,因此将执行移至finally块。打印新行时,对 println 的调用再次耗尽堆栈空间。再次抛出错误,执行移至finally当前方法调用上方的激活框架中。这使得程序f第二次打印,因为我们已经从堆栈中弹出一个激活帧,所以这个调用现在正常完成,并打印出一个新行。到目前为止,该程序已给出以下输出:

...
9152t
9153t
9154t9154f9153f              // notice we jumped to i=1953

现在,该方法再次调用自身,现在从 finally 块中调用。堆栈空间的情况和之前一样,所以程序执行如上,只是因为我们在一个finally块中进行i=1953的方法调用,所以程序执行结束在方法调用的finally块中我=1952:

9154t9154f9152f

i=9152 的 finally 块asd再次调用,传递 i=9153,并且由于现在有足够的堆栈空间来打印完整的行,该方法从 try 块输出:

9153t

然后继续调用自身,并且在此调用中将再次耗尽堆栈空间,并给出输出:

9154t9154f9153f

... 其余的输出可以用类似的方式解释:

9154t9154f9151f
9152t
9153t
9154t9154f9153f
9154t9154f9152f
9153t
9154t9154f9153f
9154t9154f9150f
9151t
9152t
9153t
...

需要注意的是:

  1. 即使在 a 的情况下也会执行 finally 块StackOverflowError
  2. 运行到 a 的程序StackOverflowError可能处于不可预测的状态。println这里唯一可观察到的效果是不输出换行符的事实。在更复杂的程序中,这可能意味着您不能相信程序正在处理的任何事情的状态,最安全的做法是完全退出。
于 2013-07-24T14:42:02.760 回答
1

当您在 finally 块中调用时,您将得到相同的错误asd- 您需要让asd堆栈上的帧返回/弹出以解决错误

于 2013-07-24T13:57:40.330 回答
1

但是由于我们已经面临 StackOverflowError,finally 块中的递归函数是如何执行的呢?

如果您尝试处理 a StackOverflowError,它只会导致进一步的后续 StackOverflowErrors,因为您正在尝试在已经满的堆栈上进行进一步的执行。你不应该试图捕捉,使用这些信息来更好地构建你的代码。这是java中未经检查的异常的要点。如果您不熟悉不同类型的异常...

检查异常

已检查异常是在编译时检查的异常,表示需要处理的条件(通过 try-catch 语句),因为它超出了程序的控制范围。例如,如果您想在Thread.sleep()给定线程内的任何地方调用,您将需要处理InterruptedException线程被不同线程中断时可能发生的可能性。由于发生这种情况的可能性在编译时是已知的,因此程序员需要处理这种情况。

未经检查的异常

java.lang.Throwable类状态...

出于对异常的编译时检查的目的,Throwable 和任何不是 RuntimeException 或 Error 的子类的 Throwable 子类都被视为已检查异常。

由于StackOverflowError是 的子类Error,因此它不被视为已检查异常,因此是“未检查”。 未经检查的异常通常由于编程错误而出现,例如在您的情况下,无限递归的错误终止。如果您尝试访问空变量的某些成员或数组中的无效索引,则可能会出现其他未经检查的异常。这些异常几乎不可能在编译时检查,并且更常用于向程序员显示其代码中的错误。

延伸阅读

Java 异常

已检查与未检查的异常

于 2013-07-24T14:47:24.533 回答