[java] Try-finally block으로 StackOverflowError 방지

다음 두 가지 방법을 살펴보십시오.

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

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

실행하면 bar()분명히 결과가 StackOverflowError나오지만 실행 foo()되지 않습니다 (프로그램은 무기한으로 실행되는 것 같습니다). 왜 그런 겁니까?



답변

영원히 실행되지 않습니다. 각 스택 오버플로로 인해 코드가 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 레벨로 각 레벨을 처리하려면 스택 깊이가 10,000 이상일 수있는 시간이 두 배가 걸립니다. 초당 10,000,000 번의 호출을 할 수 있으면 우주의 나이보다 10 ^ 3003 초 이상이 걸립니다.


답변

당신의 호출에서 예외가 발생하면 foo()내부 try, 당신은 전화 foo()에서 finally다시 재귀 시작합니다. 그로 인해 또 다른 예외가 발생하면 다른 foo()inner에서 호출 finally()하는 등 거의 모든 infinitum 입니다.


답변

다음 코드를 실행 해보십시오.

    try {
        throw new Exception("TEST!");
    } finally {
        System.out.println("Finally");
    }

예외를 던지기 전에 finally 블록이 실행된다는 것을 알 수 있습니다. (산출:

드디어

스레드 “main”java.lang.Exception의 예외 : 테스트! test.main (test.java:6)에서

마지막으로 메소드를 종료하기 직전에 호출되는 것처럼 이것은 의미가 있습니다. 그러나 일단 일단 StackOverflowError그것을 얻으면 그것을 던지려고하지만 마침내 먼저 먼저 실행해야하므로 foo()다시 실행되어 다른 스택 오버플로가 발생하고 마지막으로 다시 실행됩니다. 이것은 영원히 계속 발생하므로 예외는 실제로 인쇄되지 않습니다.

그러나 바 방법에서 예외가 발생하자마자 위의 수준까지 똑바로 던져 인쇄됩니다.


답변

이 WILL이 결국 종료 될 것이라는 합리적인 증거를 제공하기 위해 다음과 같은 의미없는 코드를 제공합니다. 참고 : Java는 가장 생생한 상상력으로 인해 내 언어가 아닙니다. 나는 베드로의 대답 지원 만이를 신혼 질문에 대한 정답을.

이렇게하면 스택 오버플로가 발생하여 호출이 발생할 수없는 경우 발생하는 상황을 시뮬레이션합니다. 사람들이 그 일을 할 수 없을 때 호출이 일어나지 않는다는 점에서 사람들이 이해하지 못하는 것이 가장 어려운 것 같습니다 .

public class Main
{
    public static void main(String[] args)
    {
        try
        {   // invoke foo() with a simulated call depth
            Main.foo(1,5);
        }
        catch(Exception ex)
        {
            System.out.println(ex.toString());
        }
    }

    public static void foo(int n, int limit) throws Exception
    {
        try
        {   // simulate a depth limited call stack
            System.out.println(n + " - Try");
            if (n < limit)
                foo(n+1,limit);
            else
                throw new Exception("StackOverflow@try("+n+")");
        }
        finally
        {
            System.out.println(n + " - Finally");
            if (n < limit)
                foo(n+1,limit);
            else
                throw new Exception("StackOverflow@finally("+n+")");
        }
    }
}

이 작은 의미없는 끈적 끈적한 결과는 다음과 같으며 실제로 포착 된 예외는 놀랍습니다. 아, 그리고 32 회 시도 (2 ^ 5)는 완전히 예상됩니다.

1 - Try
2 - Try
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
2 - Finally
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
1 - Finally
2 - Try
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
2 - Finally
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
java.lang.Exception: StackOverflow@finally(5)


답변

프로그램 추적을 배우십시오.

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는 위의 일부 레이어에서 발생하므로 다른 예외가 발생할 때까지 추가 재귀 단계를 수행 할 수 있습니다. 이것은 무한한 “루프”입니다.


답변

이 프로그램은 단순히 영원히 실행되는 것 같습니다. 실제로 종료되지만 스택 공간이 많을수록 기하 급수적으로 더 많은 시간이 걸립니다. 그것이 완료되었음을 증명하기 위해, 먼저 사용 가능한 스택 공간을 대부분 고갈시킨 다음를 호출 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);
      }
    });
  }
}

당신은 할 수 있습니다 온라인으로보십시오! (일부 실행은 foo다른 것보다 더 많거나 적은 호출을 할 수 있습니다)


답변