Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/353.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/redis/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java Try finally阻止堆栈溢出错误_Java_Recursion_Stack Overflow_Try Finally - Fatal编程技术网

Java Try finally阻止堆栈溢出错误

Java Try finally阻止堆栈溢出错误,java,recursion,stack-overflow,try-finally,Java,Recursion,Stack Overflow,Try Finally,请看以下两种方法: public static void foo() { try { foo(); } finally { foo(); } } public static void bar() { bar(); } 运行bar()。这是为什么?当您在try中调用foo()时出现异常,您最终从调用foo(),然后再次开始递归。当这导致另一个异常时,您将从另一个内部调用foo(),finally(),以此类推。请尝试运行以下代码

请看以下两种方法:

public static void foo() {
    try {
        foo();
    } finally {
        foo();
    }
}

public static void bar() {
    bar();
}


运行
bar()。这是为什么?

当您在
try
中调用
foo()
时出现异常,您最终从
调用
foo()
,然后再次开始递归。当这导致另一个异常时,您将从另一个内部调用
foo()
finally()
,以此类推。请尝试运行以下代码:

    try {
        throw new Exception("TEST!");
    } finally {
        System.out.println("Finally");
    }
您将发现finally块在抛出异常到其上面的级别之前执行。(产出:

最后

线程“main”java.lang中出现异常。异常:测试! at test.main(test.java:6)

这很有意义,因为finally是在退出方法之前调用的。但是,这意味着,一旦您得到第一个
StackOverflowError
,它将尝试抛出它,但finally必须首先执行,因此它运行
foo()
再次执行,这会导致另一个堆栈溢出,并最终再次运行。这种情况会永远发生,因此不会实际打印异常


但是,在您的bar方法中,一旦发生异常,它将直接抛出到上面的级别,并将打印出来。

学习跟踪您的程序:

public static void foo(int x) {
    System.out.println("foo " + x);
    try {
        foo(x+1);
    } 
    finally {
        System.out.println("Finally " + x);
        foo(x+1);
    }
}
这是我看到的输出:

[...]
foo 3439
foo 3440
foo 3441
foo 3442
foo 3443
foo 3444
Finally 3443
foo 3444
Finally 3442
foo 3443
foo 3444
Finally 3443
foo 3444
Finally 3441
foo 3442
foo 3443
foo 3444
[...]

如您所见,StackOverFlow是在上面的某些层抛出的,因此您可以执行额外的递归步骤,直到遇到另一个异常,依此类推。这是一个无限的“循环”.

它不会永远运行。每次堆栈溢出都会导致代码移动到finally块。问题是这将需要非常非常长的时间。时间顺序是O(2^N),其中N是最大堆栈深度

假设最大深度为5

foo() calls
    foo() calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
    finally calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
finally calls
    foo() calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
    finally calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
要将每一层工作到finally块中,需要两倍于堆栈深度的时间
10000或更多。如果你每秒可以拨打10000000次电话,这将需要10^3003秒或更长的时间。

为了提供合理的证据,证明这一现象最终会终止,我提供了以下毫无意义的代码。注意:Java不是我的语言,以任何最生动的想象。我支持把这个问题提出来只是为了支持彼得的答案,这是对这个问题的正确答案

这试图模拟调用无法发生时发生的情况,因为这会导致堆栈溢出。在我看来,人们最难理解的是,调用在无法发生时不会发生


该程序似乎永远运行;它实际上会终止,但堆栈空间越大,它所花费的时间就越长。为了证明它完成了,我编写了一个程序,首先耗尽了大部分可用堆栈空间,然后调用
foo
,最后写下发生的情况:

foo 1
  foo 2
    foo 3
    Finally 3
  Finally 2
    foo 3
    Finally 3
Finally 1
  foo 2
    foo 3
    Finally 3
  Finally 2
    foo 3
    Finally 3
Exception in thread "main" java.lang.StackOverflowError
    at Main.foo(Main.java:39)
    at Main.foo(Main.java:45)
    at Main.foo(Main.java:45)
    at Main.foo(Main.java:45)
    at Main.consumeAlmostAllStack(Main.java:26)
    at Main.consumeAlmostAllStack(Main.java:21)
    at Main.consumeAlmostAllStack(Main.java:21)
    ...
守则:

import java.util.Arrays;
import java.util.Collections;
public class Main {
  static int[] orderOfOperations = new int[2048];
  static int operationsCount = 0;
  static StackOverflowError fooKiller;
  static Error wontReachHere = new Error("Won't reach here");
  static RuntimeException done = new RuntimeException();
  public static void main(String[] args) {
    try {
      consumeAlmostAllStack();
    } catch (RuntimeException e) {
      if (e != done) throw wontReachHere;
      printResults();
      throw fooKiller;
    }
    throw wontReachHere;
  }
  public static int consumeAlmostAllStack() {
    try {
      int stackDepthRemaining = consumeAlmostAllStack();
      if (stackDepthRemaining < 9) {
        return stackDepthRemaining + 1;
      } else {
        try {
          foo(1);
          throw wontReachHere;
        } catch (StackOverflowError e) {
          fooKiller = e;
          throw done; //not enough stack space to construct a new exception
        }
      }
    } catch (StackOverflowError e) {
      return 0;
    }
  }
  public static void foo(int depth) {
    //System.out.println("foo " + depth); Not enough stack space to do this...
    orderOfOperations[operationsCount++] = depth;
    try {
      foo(depth + 1);
    } finally {
      //System.out.println("Finally " + depth);
      orderOfOperations[operationsCount++] = -depth;
      foo(depth + 1);
    }
    throw wontReachHere;
  }
  public static String indent(int depth) {
    return String.join("", Collections.nCopies(depth, "  "));
  }
  public static void printResults() {
    Arrays.stream(orderOfOperations, 0, operationsCount).forEach(depth -> {
      if (depth > 0) {
        System.out.println(indent(depth - 1) + "foo " + depth);
      } else {
        System.out.println(indent(-depth - 1) + "Finally " + -depth);
      }
    });
  }
}
导入java.util.array;
导入java.util.Collections;
公共班机{
静态int[]操作顺序=新int[2048];
静态整数运算计数=0;
静态堆栈溢出器;
静态错误wontReachHere=新错误(“无法到达此处”);
静态RuntimeException done=新建RuntimeException();
公共静态void main(字符串[]args){
试一试{
consumeralmostalstack();
}捕获(运行时异常e){
如果(e!=完成)将wontReachHere抛出;
打印结果();
扔食物杀手;
}
把它扔到这里;
}
公共静态int consumeralmostalstack(){
试一试{
int stackdepthraining=consumeralmostallstack();
如果(堆栈深度剩余<9){
返回堆栈深度剩余+1;
}否则{
试一试{
傅(1),;
把它扔到这里;
}捕获(堆栈溢出错误e){
fooKiller=e;
throw done;//堆栈空间不足,无法构造新异常
}
}
}捕获(堆栈溢出错误e){
返回0;
}
}
公共静态void foo(整数深度){
//System.out.println(“foo”+深度);没有足够的堆栈空间来执行此操作。。。
操作顺序[operationsCount++]=深度;
试一试{
foo(深度+1);
}最后{
//System.out.println(“Finally”+深度);
操作顺序[operationsCount++]=-深度;
foo(深度+1);
}
把它扔到这里;
}
公共静态字符串缩进(整数深度){
返回字符串.join(“,Collections.nCopies(depth)”);
}
公共静态无效打印结果(){
stream(orderOfOperations,0,operationsCount).forEach(深度->{
如果(深度>0){
System.out.println(缩进(深度-1)+“foo”+深度);
}否则{
System.out.println(缩进(-depth-1)+“Finally”+-depth);
}
});
}
}

您可以(一些运行可能会比其他运行调用
foo
更多或更少的次数)

大概,堆栈上没有更多空间来调用新方法时会发送StackOverflowerError(SOE)。SOE之后如何从finally调用
foo()
?@assylias:如果没有足够的空间,您将从最新的
foo()返回
invocation,并在当前
foo()
调用的
finally
块中调用
foo()
。+1到ninjalj。一旦由于溢出条件无法调用foo,您将不会从任何地方调用foo。这包括从finally块调用,这就是为什么最终会调用(宇宙时代)终止。从形式上讲,程序最终将停止,因为在处理
finally
子句期间抛出的错误将传播到下一级。但不要屏住呼吸;所采取的步骤数大约为(最大堆栈深度)的2,并且抛出异常也不便宜。这将是“正确的”但是对于
bar()
。@dan04:Java不做TCO,IIRC
foo 1
  foo 2
    foo 3
    Finally 3
  Finally 2
    foo 3
    Finally 3
Finally 1
  foo 2
    foo 3
    Finally 3
  Finally 2
    foo 3
    Finally 3
Exception in thread "main" java.lang.StackOverflowError
    at Main.foo(Main.java:39)
    at Main.foo(Main.java:45)
    at Main.foo(Main.java:45)
    at Main.foo(Main.java:45)
    at Main.consumeAlmostAllStack(Main.java:26)
    at Main.consumeAlmostAllStack(Main.java:21)
    at Main.consumeAlmostAllStack(Main.java:21)
    ...
import java.util.Arrays;
import java.util.Collections;
public class Main {
  static int[] orderOfOperations = new int[2048];
  static int operationsCount = 0;
  static StackOverflowError fooKiller;
  static Error wontReachHere = new Error("Won't reach here");
  static RuntimeException done = new RuntimeException();
  public static void main(String[] args) {
    try {
      consumeAlmostAllStack();
    } catch (RuntimeException e) {
      if (e != done) throw wontReachHere;
      printResults();
      throw fooKiller;
    }
    throw wontReachHere;
  }
  public static int consumeAlmostAllStack() {
    try {
      int stackDepthRemaining = consumeAlmostAllStack();
      if (stackDepthRemaining < 9) {
        return stackDepthRemaining + 1;
      } else {
        try {
          foo(1);
          throw wontReachHere;
        } catch (StackOverflowError e) {
          fooKiller = e;
          throw done; //not enough stack space to construct a new exception
        }
      }
    } catch (StackOverflowError e) {
      return 0;
    }
  }
  public static void foo(int depth) {
    //System.out.println("foo " + depth); Not enough stack space to do this...
    orderOfOperations[operationsCount++] = depth;
    try {
      foo(depth + 1);
    } finally {
      //System.out.println("Finally " + depth);
      orderOfOperations[operationsCount++] = -depth;
      foo(depth + 1);
    }
    throw wontReachHere;
  }
  public static String indent(int depth) {
    return String.join("", Collections.nCopies(depth, "  "));
  }
  public static void printResults() {
    Arrays.stream(orderOfOperations, 0, operationsCount).forEach(depth -> {
      if (depth > 0) {
        System.out.println(indent(depth - 1) + "foo " + depth);
      } else {
        System.out.println(indent(-depth - 1) + "Finally " + -depth);
      }
    });
  }
}