programing

Try-Finally 블록은 StackOverflowError를 방지합니다.

kingscode 2022. 7. 13. 00:11
반응형

Try-Finally 블록은 StackOverflowError를 방지합니다.

다음 두 가지 방법을 살펴보겠습니다.

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

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

입니다.bar()분명히 결과가 되다StackOverflowError, 그러나 실행 중foo()는 동작하지 않습니다(프로그램이 무기한으로 실행되고 있는 것처럼 생각됩니다).왜 그럴까?

영원히 계속되는 건 아니에요.각 스택 오버플로에 의해 코드가 최종 블록으로 이동합니다.문제는 그것이 정말, 정말 오랜 시간이 걸린다는 것이다.시간의 순서는 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()

각 레벨을 최종적으로 블록까지 처리하려면 스택의 깊이가 10,000 이상 될 수 있으므로 2배의 시간이 걸립니다.초당 1,000,000회의 통화를 할 수 있는 경우, 이 작업은 10^3003초 또는 우주의 나이보다 더 오래 걸립니다.

호출에서 예외가 발생한 경우foo()내부try, 당신이 전화한다.foo()부터finally다시 재발하기 시작합니다.이로 인해 또 다른 예외가 발생하면foo()다른 내면에서finally(), 등등 거의 무한에 가깝다.

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

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

마지막으로 블록이 실행되는 것을 알 수 있습니다.그러면 예외는 위의 레벨로 넘어갑니다.(출력:

마침내.

스레드 "main" java.lang에 예외가 있습니다.예외: TEST! at test.main(test.java:6)

이는 최종적으로 메서드를 종료하기 직전에 호출된 것이므로 의미가 있습니다.하지만, 이것은 일단 당신이 그것을 먼저 얻으면StackOverflowError를 던지려고 합니다만, 최종적으로가 먼저 실행되어야 하기 때문에 실행이 됩니다.foo()또 다른 스택오버플로우가 발생하므로 최종적으로 재실행됩니다.이 문제는 계속 발생하므로 예외는 실제로 인쇄되지 않습니다.

단, 바 방식에서는 예외가 발생하자마자 바로 위 레벨로 던져져 인쇄됩니다.

이 의지가 결국 종료될 것이라는 합리적인 증거를 제공하기 위해, 저는 다음과 같은 다소 의미 없는 코드를 제공합니다.주의: 자바는 아무리 생각해도 제 언어가 아닙니다.나는 단지 피터의 대답, 즉 질문에 대한 정답을 뒷받침하기 위해 이것을 제공한다.

이는 스택 오버플로를 발생시키기 때문에 호출이 발생할 수 없는 경우의 상태를 시뮬레이트하려고 합니다.가 보기에 사람들이 이해하지 못하는 가장 어려운 것은 발동이 일어날 수 없을 때 일어나지 않는다는 것이다.

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개의 try-call(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

언급URL : https://stackoverflow.com/questions/12438786/try-finally-block-prevents-stackoverflowerror

반응형