Чому можливо відновити з StackOverflowError?


100

Я здивований тим, як можна продовжувати виконання навіть після того, як StackOverflowErrorу Java сталося.

Я знаю, що StackOverflowErrorце підклас класу Помилка. Помилка класу розшифровується як "підклас 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
Yo dawg ... Я чув, що тобі сподобався переповнення стека, тому ми помістили переповнення стека у ваш stackoverflow.com!
П’єр Генрі

Оскільки сучасні архітектури використовують Frame Pointers для полегшення розмотування стеків, навіть часткових. Поки код + контекст для цього не потрібно динамічно розподіляти зі стека, не повинно виникнути проблем.
RBarryYoung

Відповіді:


119

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

  • перервати виконання поточно активної функції
  • видаліть його кадр стека, перейдіть до функції виклику
  • перервати виконання абонента
  • видаліть його кадр стека, перейдіть до функції виклику
  • і так далі...

... поки виняток не буде спійманий. Це нормально (насправді необхідно) і незалежно від того, який виняток кинуто і чому. Оскільки ви виймаєте виняток поза першим викликом до foo(), тисячі fooфреймів стеків, які заповнили стек, усі були розкручені, і більшість стеків можна знову використовувати.


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

1
Ви можете використовувати пункти кулі ... Я не хочу редагувати повідомлення інших людей;)
fge

1
Справа в тому, що найпотужніше fooзакінчується невизначеним станом, тому будь-який об'єкт, до якого він може торкнутися, повинен вважатись розбитим. Оскільки ви не знаєте, в якій функції переповнення стека відбулося, лише tryпідозрюваний будь-який об'єкт, який може бути змінений будь-яким способом, доступним звідти. Зазвичай з’ясовувати, що сталося, і намагатися виправити це не варто.
Саймон Ріхтер

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

1
@SimonRichter Ні, питання досить конкретне. Мова не йде про поводження з Errors. ОП задається лише питанням StackOverflowError, і він запитує конкретну річ, що стосується цієї помилки: як виклик методу не може вийти з ладу, коли ця помилка виявлена.
Бакуріу

23

Коли StackOverflowError кинуто, стек заповнений. Однак, коли це спіймано , усі ці fooдзвінки вискочили зі стека. barможе працювати нормально, оскільки стек більше не переповнюється foos. (Зверніть увагу, що я не думаю, що JLS гарантує, що ви можете відновитись після переповнення стека таким чином.)


12

Коли станеться StackOverFlow, JVM спливе до улову, звільнивши стек.

У вашому прикладі, він отримує позбавлення від усіх складених фонів.


8

Тому що стек насправді не переповнюється. Краще ім'я може бути AttemptToOverflowStack. По суті, це означає, що остання спроба коригування кадру стека помиляється, оскільки на стеку не залишилося достатньо вільного місця. Стек насправді може залишити багато місця, просто не вистачить місця. Отже, будь-яка операція залежала б від успішного виклику (як правило, виклик методу), ніколи не буде виконано, і все, що залишилося, - це програма, яка впорається з цим фактом. Що означає, що він насправді нічим не відрізняється від будь-якого іншого винятку. Фактично, ви можете зловити виняток у функції, яка здійснює дзвінок.


1
Будьте обережні, якщо ви зробите це, щоб ваш обробник винятків не потребував більше місця у стеці, ніж є!
Вінс

2

Як уже було сказано , можна виконати код і, зокрема, викликати функції, після StackOverflowErrorвилучення a, тому що звичайна процедура обробки винятків JVM розкручує стек між і throwта catchточками, звільняючи простір стека для використання. І ваш експеримент підтверджує, що це так.

Однак це не зовсім те саме, що говорити про те, що в цілому можливо відновитись після StackOverflowError.

StackOverflowErrorIS-A VirtualMachineError, який IS-AN Error. Як ви зазначаєте, Java надає кілька невиразних порад щодо Error:

вказує на серйозні проблеми, які розумна програма не повинна намагатися наздогнати

і ви, резонно зробити висновок , що має звучить як ловити Errorможе бути добре в деяких обставинах. Зауважте, що виконання одного експерименту не демонструє, що щось взагалі безпечно зробити. Це можуть робити лише правила мови Java та технічні характеристики класів, які ви використовуєте. A VirtualMachineError- це особливий клас винятку, оскільки специфікація мови Java та специфікація віртуальної машини Java надають інформацію про семантику цього винятку. Зокрема, останній говорить :

Реалізація віртуальної машини Java кидає об'єкт, який є примірником підкласу класу, VirtualMethodErrorколи внутрішня помилка чи обмеження ресурсів заважає йому реалізувати семантику, описану в цьому розділі. Ця специфікація не може передбачити, де можуть виникати внутрішні помилки або обмеження ресурсів, і не вимагає точно, коли про них можна повідомити. Таким чином, будь-який із VirtualMethodErrorвизначених нижче підкласів може бути кинутий у будь-який час під час роботи віртуальної машини Java:

...

  • StackOverflowError: У реалізації Java Virtual Machine не вистачає місця для стеку для потоку, як правило, через те, що потік виконує необмежену кількість рекурсивних викликів внаслідок помилки у виконанні програми.

Найважливішою проблемою є те, що ви "не можете передбачити", де або коли StackOverflowErrorбуде кинуто повідомлення. Немає гарантій щодо того, куди його не кинуть. Ви не можете розраховувати на те, що він буде кинутий при вступі до методу, наприклад. Це може бути кинуто в точку в рамках методу.

Ця непередбачуваність потенційно згубна. Оскільки це може бути кинуто у межах методу, його можна перекинути частиною через послідовність операцій, які клас вважає однією "атомною" операцією, залишаючи об'єкт у частково модифікованому, непослідовному стані. Якщо об'єкт знаходиться в непослідовному стані, будь-яка спроба використання цього об'єкта може спричинити помилкову поведінку. У всіх практичних випадках ви не можете знати, який об’єкт знаходиться в непослідовному стані, тому ви повинні припустити, що жоден об’єкт не є надійним. Отже, будь-яка операція відновлення або спроба продовження після вилучення винятку може мати помилкову поведінку. Тому єдине безпечне, що потрібно зробити, це не зловити aStackOverflowError, а скоріше, щоб дозволити програмі припинити роботу. (На практиці ви можете спробувати зробити деякий журнал помилок, щоб полегшити усунення несправностей, але ви не можете розраховувати на те, що журнал працює правильно). Тобто ви не можете надійно відновитись із aStackOverflowError .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.