Warum ist es möglich, von einem StackOverflowError wiederherzustellen?


100

Ich bin überrascht, wie es möglich ist, die Ausführung auch nach einem StackOverflowErrorEreignis in Java fortzusetzen .

Ich weiß, dass dies StackOverflowErroreine Unterklasse der Klasse Error ist. Die Klasse Error wird als "eine Unterklasse von Throwable deklariert, die auf schwerwiegende Probleme hinweist, die eine vernünftige Anwendung nicht abfangen sollte".

Dies klingt eher nach einer Empfehlung als nach einer Regel, da das Abfangen eines Fehlers wie eines StackOverflowError tatsächlich zulässig ist und es der Vernünftigkeit des Programmierers überlassen ist, dies nicht zu tun. Und siehe da, ich habe diesen Code getestet und er wird normal beendet.

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");
    }
}

Wie kann das sein? Ich denke, bis der StackOverflowError ausgelöst wird, sollte der Stack so voll sein, dass kein Platz mehr zum Aufrufen einer anderen Funktion vorhanden ist. Läuft der Fehlerbehandlungsblock in einem anderen Stapel oder was ist hier los?



57
Ich mache die ganze Zeit Fehler bei StackOverflow. Hält mich aber nicht davon ab, wiederzukommen.

10
Yo dawg ... Ich habe gehört, dass Sie Stapelüberläufe mögen, also haben wir einen Stapelüberlauf in Ihr stackoverflow.com eingefügt!
Pierre Henry

Weil moderne Architekturen Rahmenzeiger verwenden, um das Abwickeln von Stapeln zu erleichtern, auch von Teilstapeln. Solange der Code + Kontext dafür nicht dynamisch vom Stapel zugewiesen werden muss, sollte es kein Problem geben.
RBarryYoung

Antworten:


119

Wenn der Stapel überläuft und StackOverflowErrorgeworfen wird, wird der Stapel durch die übliche Ausnahmebehandlung abgewickelt. Das Abwickeln des Stapels bedeutet:

  • brechen Sie die Ausführung der aktuell aktiven Funktion ab
  • Löschen Sie den Stapelrahmen und fahren Sie mit der aufrufenden Funktion fort
  • brechen Sie die Ausführung des Anrufers ab
  • Löschen Sie den Stapelrahmen und fahren Sie mit der aufrufenden Funktion fort
  • und so weiter...

... bis die Ausnahme abgefangen wird. Dies ist normal (tatsächlich notwendig) und unabhängig davon, welche Ausnahme ausgelöst wird und warum. Da Sie die Ausnahme außerhalb des ersten Aufrufs von abfangen foo(), wurden die Tausenden von fooStapelrahmen, die den Stapel füllten, alle abgewickelt, und der größte Teil des Stapels kann wieder verwendet werden.


1
@fge Fühlen Sie sich frei zu bearbeiten, ich dachte über einen Absatzumbruch nach, konnte aber keinen Ort finden, an dem es gut aussah.

1
Sie könnten Aufzählungszeichen verwenden ... Ich zögere es,
Beiträge

1
Der Punkt ist, dass das Innerste foomit einem undefinierten Zustand endet, so dass angenommen werden muss, dass jedes Objekt, das es berührt hat, gebrochen ist. Da Sie nicht wissen, in welcher Funktion der Stapelüberlauf aufgetreten ist, sondern nur, dass es sich um einen Nachkommen des tryBlocks handeln muss, der ihn abgefangen hat, ist jedes Objekt verdächtig, das mit einer von dort aus erreichbaren Methode geändert werden kann. Normalerweise lohnt es sich nicht, herauszufinden, was passiert ist, und zu versuchen, das Problem zu beheben.
Simon Richter

2
@delnan, ich denke die Antwort ist unvollständig, ohne auch ins Detail zu gehen, warum dies eine schlechte Idee ist. Der Unterschied zu einer explizit ausgelösten Ausnahme besteht darin, dass Errors selbst beim Schreiben von ausnahmesicherem Code nicht vorweggenommen werden kann.
Simon Richter

1
@ SimonRichter Nein, die Frage ist ziemlich spezifisch. Es geht nicht um den Umgang mit Errors. Das OP fragt nur nach StackOverflowErrorund fragt nach einer bestimmten Frage zur Behandlung dieses Fehlers: Wie kann ein Methodenaufruf nicht fehlschlagen, wenn dieser Fehler abgefangen wird?
Bakuriu

23

Wenn der StackOverflowError ausgelöst wird, ist der Stapel voll. Wenn es jedoch abgefangen wird , wurden alle diese fooAufrufe vom Stapel genommen. barkann normal ausgeführt werden, da der Stapel nicht mehr mit foos überfüllt ist . (Beachten Sie, dass ich nicht glaube, dass das JLS garantiert, dass Sie sich von einem Stapelüberlauf wie diesem erholen können.)


12

Wenn der StackOverFlow auftritt, wird die JVM zum Fang heruntergefahren und der Stapel freigegeben.

In Ihrem Beispiel werden alle gestapelten Foo befreit.


8

Weil der Stapel nicht wirklich überläuft. Ein besserer Name könnte AttemptToOverflowStack sein. Grundsätzlich bedeutet dies, dass der letzte Versuch, den Stapelrahmen anzupassen, fehlschlägt, weil nicht genügend freier Speicherplatz auf dem Stapel vorhanden ist. Der Stapel könnte tatsächlich viel Platz übrig haben, nur nicht genug Platz. Unabhängig davon, welche Operation vom erfolgreichen Aufruf des Aufrufs abhängt (normalerweise ein Methodenaufruf), wird sie nie ausgeführt, und das Programm muss sich nur noch mit dieser Tatsache befassen. Das heißt, es unterscheidet sich wirklich nicht von anderen Ausnahmen. Tatsächlich könnten Sie die Ausnahme in der Funktion abfangen, die den Aufruf ausführt.


1
Seien Sie dabei vorsichtig, dass Ihr Ausnahmebehandler nicht mehr Stapelspeicherplatz benötigt als verfügbar ist!
Vince

2

Wie bereits beantwortet wurde , ist es möglich, Code auszuführen und insbesondere Funktionen aufzurufen, nachdem a StackOverflowErrorabgefangen wurde, da die normale Ausnahmebehandlungsprozedur der JVM den Stapel zwischen den throwund den catchPunkten abwickelt und so Stapelspeicherplatz für Sie frei macht. Und Ihr Experiment bestätigt, dass dies der Fall ist.

Dies ist jedoch nicht ganz das Gleiche wie die Aussage, dass es im Allgemeinen möglich ist, sich von a zu erholenStackOverflowError .

Ein StackOverflowErrorIS-A VirtualMachineError, der IS-AN ist Error. Wie Sie hervorheben, bietet Java einige vage Ratschläge für Error:

weist auf schwerwiegende Probleme hin, die eine vernünftige Anwendung nicht abfangen sollte

und Sie kommen vernünftigerweise zu dem Schluss, dass es unter bestimmtenError Umständen in Ordnung sein sollte , einen zu fangen . Beachten Sie, dass die Durchführung eines Experiments nicht zeigt, dass etwas im Allgemeinen sicher ist. Dies können nur die Regeln der Java-Sprache und die Spezifikationen der von Ihnen verwendeten Klassen. A VirtualMachineErrorist eine spezielle Ausnahmeklasse, da die Java-Sprachspezifikation und die Java Virtual Machine-Spezifikation Informationen zur Semantik dieser Ausnahme enthalten. Insbesondere sagt der letztere :

Eine Java Virtual Machine-Implementierung löst ein Objekt aus, das eine Instanz einer Unterklasse der Klasse ist, VirtualMethodErrorwenn ein interner Fehler oder eine Ressourcenbeschränkung die Implementierung der in diesem Kapitel beschriebenen Semantik verhindert. Diese Spezifikation kann nicht vorhersagen, wo interne Fehler oder Ressourcenbeschränkungen auftreten können, und schreibt nicht genau vor, wann sie gemeldet werden können. Somit kann jede der VirtualMethodErrorunten definierten Unterklassen jederzeit während des Betriebs der Java Virtual Machine ausgelöst werden:

...

  • StackOverflowError: Der Implementierung der Java Virtual Machine ist der Stapelspeicher für einen Thread ausgegangen, normalerweise, weil der Thread aufgrund eines Fehlers im ausführenden Programm eine unbegrenzte Anzahl rekursiver Aufrufe ausführt.

Das entscheidende Problem ist, dass Sie "nicht vorhersagen können", wo oder wann ein StackOverflowErrorWurf geworfen wird. Es gibt keine Garantie dafür, wo es nicht geworfen wird. Sie können sich nicht darauf verlassen , dass es beispielsweise beim Eintritt in eine Methode ausgelöst wird . Es könnte an einem Punkt innerhalb einer Methode geworfen werden.

Diese Unvorhersehbarkeit ist möglicherweise katastrophal. Da es innerhalb einer Methode ausgelöst werden kann, kann es teilweise durch eine Folge von Operationen geworfen werden, die die Klasse als eine "atomare" Operation betrachtet, wodurch das Objekt in einem teilweise modifizierten, inkonsistenten Zustand belassen wird. Wenn sich das Objekt in einem inkonsistenten Zustand befindet, kann jeder Versuch, dieses Objekt zu verwenden, zu einem fehlerhaften Verhalten führen. In allen praktischen Fällen können Sie nicht wissen, welches Objekt sich in einem inkonsistenten Zustand befindet. Daher müssen Sie davon ausgehen, dass keine Objekte vertrauenswürdig sind. Jeder Wiederherstellungsvorgang oder Versuch, nach dem Abfangen der Ausnahme fortzufahren, kann daher ein fehlerhaftes Verhalten aufweisen. Das einzig sichere ist daher, a nicht zu fangenStackOverflowError, sondern um das Programm beenden zu lassen. (In der Praxis versuchen Sie möglicherweise, eine Fehlerprotokollierung durchzuführen, um die Fehlerbehebung zu unterstützen. Sie können sich jedoch nicht darauf verlassen, dass diese Protokollierung ordnungsgemäß funktioniert.) Das heißt, Sie können sich nicht zuverlässig von einem erholenStackOverflowError .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.