Try-finally block prevents StackOverflowError

后端 未结 6 1920
野趣味
野趣味 2020-12-04 04:54

Take a look at the following two methods:

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

public static void bar(         


        
6条回答
  •  心在旅途
    2020-12-04 05:10

    The program merely seems to run forever; it actually terminates, but it takes exponentially more time the more stack space you have. To prove that it finishes, I wrote a program that first depletes most of the available stack space, and then calls foo, and finally writes a trace of what happened:

    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)
        ...
    

    The code:

    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);
          }
        });
      }
    }
    

    You can try it online! (Some runs might call foo more or fewer times than others)

提交回复
热议问题