Почему возможно восстановление после StackOverflowError?


100

Я удивлен тем, как можно продолжить выполнение даже после того, как StackOverflowErrorв Java произошло.

Я знаю, что StackOverflowErrorэто подкласс класса Error. Класс Error декументируется как «подкласс Throwable, который указывает на серьезные проблемы, которые разумное приложение не должно пытаться уловить».

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

public class Test
{
    public static void main(String[] args)
    {
        try {
            foo();
        } catch (StackOverflowError e) {
            bar();
        }
        System.out.println("normal termination");
    }

    private static void foo() {
        System.out.println("foo");
        foo();
    }

    private static void bar() {
        System.out.println("bar");
    }
}

Как это может быть? Я думаю, что к моменту возникновения ошибки StackOverflowError стек должен быть настолько заполнен, чтобы не было места для вызова другой функции. Работает ли блок обработки ошибок в другом стеке или что здесь происходит?



57
Я все время делаю ошибки в StackOverflow. Но это не мешает мне вернуться.

10
Йо, чувак ... Я слышал, вам понравилось переполнение стека, поэтому мы помещаем переполнение стека в ваш stackoverflow.com!
Пьер Анри

Потому что современные архитектуры используют указатели кадров для облегчения раскрутки стеков, даже частичных. Пока код + контекст для этого не должны динамически выделяться из стека, проблем быть не должно.
RBarryYoung 03

Ответы:


119

Когда стек переполняется и StackOverflowErrorвыбрасывается, обычная обработка исключений раскручивает стек. Раскрутка стопки означает:

  • прервать выполнение текущей активной функции
  • удалите его кадр стека, продолжите вызывающую функцию
  • прервать выполнение вызывающего
  • удалите его кадр стека, продолжите вызывающую функцию
  • и так далее...

... пока не будет обнаружено исключение. Это нормально (фактически, необходимо) и не зависит от того, какое исключение выбрано и почему. Поскольку вы перехватываете исключение за пределами первого вызова foo(), все тысячи fooкадров стека, заполнившие стек, были размотаны, и большая часть стека может быть использована снова.


1
@fge Не стесняйтесь редактировать, я подумал о разрыве абзаца, но не смог найти места, где он выглядел бы хорошо.

1
Вы можете использовать
маркированный список

1
Дело в том, что самое внутреннее fooзавершается неопределенным состоянием, поэтому любой объект, которого он мог коснуться, должен считаться сломанным. Поскольку вы не знаете, в какой функции произошло переполнение стека, только то, что он должен быть потомком tryблока, который его поймал, любой объект, который может быть изменен любым доступным оттуда методом, теперь подозревается. Обычно не стоит выяснять, что случилось, и пытаться это исправить.
Саймон Рихтер

2
@delnan, я думаю, что ответ неполный, если не вдаваться в подробности, почему это плохая идея. Отличие от явного исключения состоит в том, что Errors нельзя предвидеть даже при написании кода, безопасного для исключений.
Саймон Рихтер

1
@SimonRichter Нет, вопрос довольно конкретный. Дело не в обращении Errorс собой. OP спрашивает только о том StackOverflowError, и он спрашивает конкретную вещь об обработке этой ошибки: как может вызов метода не потерпеть неудачу при обнаружении этой ошибки.
Bakuriu 02

23

Когда выбрасывается StackOverflowError, стек заполнен. Однако когда он перехватывается , все эти fooвызовы выталкиваются из стека. barможет работать нормально, потому что стек больше не переполняется foos. (Обратите внимание, что я не думаю, что JLS гарантирует, что вы сможете восстановиться после такого переполнения стека.)


12

Когда происходит StackOverFlow, JVM выскакивает на защелку, освобождая стек.

В вашем примере он избавляется от всех сложенных файлов foo.


8

Потому что стек на самом деле не переполняется. Лучшее имя может быть AttemptToOverflowStack. В основном это означает, что последняя попытка настроить фрейм стека дает ошибку, потому что в стеке недостаточно свободного места. На самом деле в стеке может быть много места, но недостаточно места. Таким образом, какая бы операция ни зависела от успешного выполнения вызова (обычно это вызов метода), она никогда не выполняется, и все, что остается программе, - это иметь дело с этим фактом. Это означает, что это действительно ничем не отличается от любого другого исключения. Фактически, вы можете поймать исключение в функции, выполняющей вызов.


1
Просто будьте осторожны, если вы сделаете это, чтобы обработчику исключений не требовалось больше места в стеке, чем доступно!
Винс

2

Как уже было сказано, после перехвата a можно выполнять код и, в частности, вызывать функции, StackOverflowErrorпоскольку обычная процедура обработки исключений JVM раскручивает стек между точками throwи catchточками, освобождая пространство стека для использования. И ваш эксперимент подтверждает это.

Однако, это не совсем то же самое , как сказать , что это, в общем, можно восстановить из StackOverflowError.

StackOverflowErrorIS-A VirtualMachineError, который IS-AN Error. Как вы отметили, Java дает несколько расплывчатых советов для Error:

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

и вы, резонно заключить , что должно звучит как ловить Errorможет быть хорошо в некоторых обстоятельствах. Обратите внимание, что выполнение одного эксперимента не демонстрирует, что что-то в целом безопасно. Это могут сделать только правила языка Java и спецификации используемых вами классов. A VirtualMachineError- это особый класс исключения, поскольку спецификация языка Java и спецификация виртуальной машины Java предоставляют информацию о семантике этого исключения. В частности, последний говорит :

Реализация виртуальной машины Java создает объект, являющийся экземпляром подкласса класса, VirtualMethodErrorкогда внутренняя ошибка или ограничение ресурсов не позволяют реализовать семантику, описанную в этой главе. Эта спецификация не может предсказать, где могут возникнуть внутренние ошибки или ограничения ресурсов, и не требует точного определения того, когда о них можно сообщить. Таким образом, любой из VirtualMethodErrorподклассов, определенных ниже, может быть запущен в любое время во время работы виртуальной машины Java:

...

  • StackOverflowError: Реализация виртуальной машины Java исчерпала пространство стека для потока, как правило, из-за того, что поток выполняет неограниченное количество рекурсивных вызовов в результате ошибки в выполняющейся программе.

Ключевой проблемой является то, что вы «не можете предсказать», где и когда StackOverflowErrorбудет брошен. Нет никаких гарантий, куда он не будет брошен. Например, вы не можете полагаться на то, что он будет брошен при входе в метод. Его можно бросить в точку внутри метода.

Эта непредсказуемость потенциально губительна. Поскольку он может быть брошен внутри метода, он может быть брошен частично через последовательность операций, которые класс считает одной «атомарной» операцией, оставляя объект в частично измененном, несовместимом состоянии. Когда объект находится в несовместимом состоянии, любая попытка использовать этот объект может привести к ошибочному поведению. Во всех практических случаях вы не можете знать, какой объект находится в несогласованном состоянии, поэтому вы должны предположить, что ни один объект не заслуживает доверия. Следовательно, любая операция восстановления или попытка продолжить после обнаружения исключения могут иметь ошибочное поведение. Поэтому единственное безопасное решение - не пойматьStackOverflowError, а скорее позволить программе завершить работу. (На практике вы можете попытаться вести журнал ошибок, чтобы помочь в устранении неполадок, но вы не можете полагаться на правильную работу этого журнала). То есть из файлаStackOverflowError .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.