Java中是否可以进行错误恢复?

时间:2015-04-03 10:31:43

标签: java error-handling

考虑以下生成StackOverflowError的代码。

public class SimpleProgram {
  static SimpleProgram s = new SimpleProgram();

  public static void main(String[] args) {
      s.f(0);
  }

  void f(int i) {
      System.out.println("f :" + (++i));
      g(i);
  }

  void g(int i) {
      System.out.println("g :" + (++i));
      f(i);
  }
}

它只是打印从1到n的数字(' 5417'用f()执行时)。之后它抛出StackOverflowError并终止。现在考虑第二个程序。

public class SimpleProgram{
  static SimplePrograms = new SimpleProgram();

  public static void main(String[] args) {
      s.f(0);
  }

  void f(int i) {
      try {
          System.out.println("f :" + (++i));
          g(i);
      } catch (StackOverflowError e) {
          System.out.println("f :" + (++i));
          g(i);
      }
  }

  void g(int i) {
      try {
          System.out.println("g :" + (++i));
          f(i);
      } catch (StackOverflowError e) {
          System.out.println("g :" + (++i));
          f(i);
      }
  }
}

现在它显示了一些奇怪的行为。程序没有按预期终止,但显示的值重复出现(比如f:4107 g:4108到f:4120再次回到f:4107)。

我的问题是,为什么会发生这种情况?我认为像StackOverflowError这样的错误意味着当前线程的堆栈已满,因此无法恢复。程序必须强行停止它的执行,即不调用下一个函数,但是没有发生。 JVM线程堆栈可以根据需要增加其大小吗?

2 个答案:

答案 0 :(得分:2)

您的异常处理程序会进行更多方法调用(System.out.println()f()g())。这些调用可能会抛出一个新的StackOverflowError,它可能会在调用堆栈中被捕获几个级别,因此您将看到数字下降并无限备份(因为异常会导致调用堆栈,调用堆栈中释放了一些空间,因此可以进行额外的调用。

这是我得到的输出的一部分(为了更清楚,我添加了#34; catch"到catch块内的println语句):

StackOverflowError尝试呼叫System.out.println(String)时发生第一次println(),这就是g :9662catch g :9663中没有显示新行的原因。出于某种原因,在第一个StackOverflowError之后,在我们获得另一个StackOverflowError之前,还有大约50个额外呼叫的空间,之后错误会更频繁地发生。

... no eception till this point...
g :9660
f :9661
g :9662catch g :9663 
f :9664
g :9665
f :9666
g :9667
f :9668
g :9669
f :9670
g :9671
f :9672
g :9673
f :9674
g :9675
f :9676
g :9677
f :9678
g :9679
f :9680
g :9681
f :9682
g :9683
f :9684
g :9685
f :9686
g :9687
f :9688
g :9689
f :9690
g :9691
f :9692
g :9693
f :9694
g :9695
f :9696
g :9697
f :9698
g :9699
f :9700
g :9701
f :9702
g :9703catch g :9704catch f :9703
g :9704catch g :9705catch g :9702
f :9703
g :9704catch g :9705catch f :9704
g :9705catch g :9706catch f :9701
g :9702
f :9703
g :9704catch g :9705catch f :9704
g :9705catch g :9706catch g :9703
f :9704
g :9705catch g :9706catch f :9705
g :9706catch g :9707catch g :9700
f :9701
g :9702
f :9703
g :9704catch g :9705catch f :9704
g :9705catch g :9706catch g :9703
f :9704
g :9705catch g :9706catch f :9705
g :9706catch g :9707catch f :9702
g :9703
f :9704
g :9705catch g :9706catch f :9705
g :9706catch g :9707catch g :9704
f :9705
....

答案 1 :(得分:2)

您所看到的与构建程序的方式有关,而不是与Java处理错误的方式有关。您的程序在每个n调用级别创建两个重试点,因此通过该程序的路径总数呈指数级增长。

让我们考虑如果只有三个调用级别足以溢出堆栈会发生什么:

main -> f(0)
f(0) -> g(0)
g(0) -> f(1)
f(1) -> g(1)
g(1) -> f(2)
f(2) -> g(2)
g(2) -> f(3) <<== Stack overflow

接下来会发生什么?堆栈开始展开。每个方法都为StackOverflowError安装了一个处理程序,因此他们将处理错误并再次调用相同的函数:

g(2) handler -> f(3) <<== Stack overflow #2
f(2) handler -> g(2)
g(2) -> f(3)         <<== Stack overflow #3
g(2) handler -> f(3) <<== Stack overflow #4
g(1) handler -> f(2)
f(2) -> g(2)
g(2) -> f(3)         <<== Stack overflow #5
f(1) handler -> g(1)
g(1) -> f(2)
f(2) -> g(2)
g(2) -> f(3)         <<== Stack overflow #6
g(0) handler -> f(1)
f(1) -> g(1)
g(1) -> f(2)
f(2) -> g(2)
g(2) -> f(3)         <<== Stack overflow #7
f(0) handler -> g(0)
g(0) -> f(1)
f(1) -> g(1)
g(1) -> f(2)
f(2) -> g(2)
g(2) -> f(3)         <<== Stack overflow #8

正如你所看到的,这条链最终会展开,但它只会上下三次,只有三个级别。有数千个级别,数字会更高:在溢出的路上,每个fg级别的错误计数会增加两倍,因此它是2 n 表示n帧。成千上万的{{​​1}},你可以认为你的程序是无限的。