Блок Try-finally предотвращает StackOverflowError

Взгляните на следующие два метода:

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

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

Запуск bar() явно приводит к StackOverflowError, но запуск foo() не выполняется (программа просто работает бесконечно). Почему это?

Ответ 1

Это не работает вечно. Каждое переполнение стека заставляет код перемещаться в блок 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 секунды или дольше, чем возраст Вселенной.

Ответ 2

Когда вы получаете исключение из вызова foo() внутри try, вы вызываете foo() из finally и снова начинаете рекурсию. Когда это вызывает другое исключение, вы вызываете foo() из другого внутреннего finally() и т.д. Почти до бесконечности.

Ответ 3

Попробуйте запустить следующий код:

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

Вы обнаружите, что блок finally выполняется перед тем, как выбросить Exception до уровня выше него. (Выход:

Наконец

Исключение в потоке "main" java.lang.Exception: TEST!     на test.main(test.java:6)

Это имеет смысл, так как, наконец, вызывается прямо перед выходом из метода. Это означает, однако, что, как только вы получите этот первый StackOverflowError, он попытается выбросить его, но, наконец, он должен выполнить сначала, поэтому он снова запускает foo(), который получает еще одно переполнение стека, и, как таковое, снова запускается снова. Это продолжается вечно, поэтому исключение никогда не печатается.

Однако в вашем методе бара, как только возникает исключение, он просто подбрасывается прямо до уровня выше и будет напечатан

Ответ 4

В целях предоставления разумных доказательств того, что эта В конце концов закончится, я предлагаю следующий довольно бессмысленный код. Примечание: Java не является моим языком, каким-то образом из самых ярких фантазий. Я предлагаю это только для поддержки ответа Питера, что является правильным ответом на вопрос.

Это попытка имитировать условия того, что происходит, когда invoke не может произойти, потому что это приведет к переполнению стека. Мне кажется, что самые трудные люди не понимают, что вызов не происходит, когда этого не может случиться.

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("[email protected]("+n+")");
        }
        finally
        {
            System.out.println(n + " - Finally");
            if (n < limit)
                foo(n+1,limit);
            else
                throw new Exception("[email protected]("+n+")");
        }
    }
}

Результат этой маленькой бессмысленной кучи goo следующий: фактическое исключение может стать сюрпризом; Ох и 32 try-calls (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: [email protected](5)

Ответ 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 выбрасывается на несколько слоев выше, поэтому вы можете выполнять дополнительные шаги рекурсии, пока не нажмете другое исключение, и так далее. Это бесконечный "цикл".