Взгляните на следующие два метода:
public static void foo() {
try {
foo();
} finally {
foo();
}
}
public static void bar() {
bar();
}
Запуск bar()
явно приводит к a StackOverflowError
, но запуск foo()
- нет (кажется, что программа работает бесконечно). Это почему?
java
recursion
stack-overflow
try-finally
arshajii
источник
источник
finally
предложения, будут распространяться на следующий уровень. Но не задерживай дыхание; количество предпринятых шагов составит около 2 (максимальная глубина стека), и создание исключений также не совсем дешево.bar()
, хотя.Ответы:
Это не вечно. При каждом переполнении стека код перемещается в блок finally. Проблема в том, что это займет очень много времени. Порядок времени O (2 ^ N), где N - максимальная глубина стека.
Представьте себе максимальную глубину 5
Чтобы проработать каждый уровень в блоке finally, нужно вдвое больше, а глубина стека может составлять 10 000 и более Если вы можете делать 10 000 000 вызовов в секунду, это займет 10 ^ 3003 секунды или дольше, чем возраст вселенной.
источник
-Xss
, я получу глубину [150 - 210], так что 2 ^ n в конечном итоге будет [47 - 65] цифрой. Не буду ждать так долго, это достаточно близко к бесконечности для меня.foo
наконец, завершится, это приведет кStackOverflowError
?Когда вы получите исключение из вызова
foo()
внутриtry
, вы звонитеfoo()
сfinally
и начать снова рекурсию. Когда это вызывает другое исключение, вы будете звонитьfoo()
из другого внутреннегоfinally()
и т. Д. Почти до бесконечности .источник
foo()
позвонить из, наконец, после SOE?foo()
вызова и вызовете егоfoo()
вfinally
блоке текущегоfoo()
вызова.Попробуйте запустить следующий код:
Вы обнаружите, что блок finally выполняется, прежде чем выбросить исключение на уровень выше него. (Вывод:
Это имеет смысл, так как, наконец, вызывается перед выходом из метода. Это означает, однако, что, как только вы получите это первым
StackOverflowError
, он попытается выбросить его, но, наконец, сначала должен выполняться, так что он запускаетсяfoo()
снова, что вызывает другое переполнение стека, и, как таковое, запускается, наконец, снова. Это происходит вечно, поэтому исключение никогда не печатается.Однако в вашем методе bar, как только возникает исключение, оно просто выбрасывается прямо на уровень выше и будет напечатано
источник
В попытке представить разумные доказательства того, что это в конечном итоге прекратится, я предлагаю следующий довольно бессмысленный код. Примечание: Java не является моим языком, по всей видимости. Я тух это вверх только поддержать ответ Питера, который правильный ответ на этот вопрос.
Это пытается смоделировать условия того, что происходит, когда вызов НЕ может произойти, потому что это приведет к переполнению стека. Мне кажется, что самое трудное, что люди не могут понять, это то, что призыв не происходит, когда он не может произойти.
Результат этой маленькой бессмысленной груды слизи следующий, и фактическое обнаруженное исключение может стать неожиданностью; Да, и 32 попытки вызова (2 ^ 5), что вполне ожидаемо:
источник
Научитесь отслеживать свою программу:
Это вывод, который я вижу:
Как вы можете видеть, StackOverFlow создается на нескольких уровнях выше, поэтому вы можете выполнять дополнительные шаги рекурсии, пока не достигнете другого исключения, и так далее. Это бесконечная «петля».
источник
foo
второй раз, вfinally
блоке он больше не находится вtry
. Таким образом, хотя он будет возвращаться вниз по стеку и создавать дополнительные переполнения стека один раз, во второй раз он просто сбросит ошибку, вызванную вторым вызовомfoo
, вместо повторного углубления.Программа, кажется, работает вечно; это фактически завершается, но это занимает экспоненциально больше времени, чем больше у вас стекового пространства. Чтобы доказать, что это заканчивается, я написал программу, которая сначала истощает большую часть доступного пространства стека, а затем вызывает
foo
и, наконец, записывает след того, что произошло:Код:
Вы можете попробовать это онлайн! (Некоторые пробеги могут вызывать
foo
больше или меньше раз, чем другие)источник