it-swarm.com.de

Wie gehe ich mit StackOverflowError in Java um?

Wie gehe ich mit StackOverflowError in Java um?

16
Silent Warrior

Ich bin mir nicht sicher, was Sie mit "Griff" meinen.

Sie können diesen Fehler sicherlich fangen:

public class Example {
    public static void endless() {
        endless();
    }

    public static void main(String args[]) {
        try {
            endless();
        } catch(StackOverflowError t) {
            // more general: catch(Error t)
            // anything: catch(Throwable t)
            System.out.println("Caught "+t);
            t.printStackTrace();
        }
        System.out.println("After the error...");
    }
}

dies ist jedoch höchstwahrscheinlich eine schlechte Idee, wenn Sie nicht genau wissen, was Sie tun.

16
Huxi

Sie haben wahrscheinlich eine unendliche Rekursion im Gange.

Das heißt eine Methode, die sich immer wieder aufruft

public void sillyMethod()
{
    sillyMethod();
}

Um dies zu beheben, müssen Sie den Code so korrigieren, dass die Rekursion endet und nicht für immer fortgesetzt wird.

17
Andrew Bullock

Schauen Sie sich den Beitrag von Raymond Chen an. Beim Debuggen eines Stapelüberlaufs möchten Sie sich auf den sich wiederholenden rekursiven Teil konzentrieren Ein Auszug:

Wenn Sie Ihre Defekt-Tracking-Datenbank durchsuchen und feststellen, ob dies ein bekanntes Problem ist oder nicht, ist es unwahrscheinlich, dass die Suche nach den Top-Funktionen auf dem Stack etwas Interessantes findet. Dies liegt daran, dass Stapelüberläufe zu einem zufälligen Zeitpunkt in der Rekursion auftreten. Jeder Stapelüberlauf unterscheidet sich oberflächlich von jedem anderen, selbst wenn es sich um denselben Stapelüberlauf handelt. 

Angenommen, Sie singen das Lied Frère Jacques, nur dass Sie jeden Vers ein paar Töne höher als der vorherige singen. Irgendwann werden Sie die Spitze Ihres Gesangsbereichs erreichen, und wo genau dies geschieht, hängt davon ab, wo sich Ihre Stimmgrenze an der Melodie befindet. In der Melodie sind die ersten drei Noten jeweils ein neues "Rekordhoch" (dh die Noten sind höher als alle anderen gesungenen Noten), und neue Rekordhöhen erscheinen in den drei Noten des dritten Taktes und eine letzte Platte hoch in der zweiten Note des fünften Taktes.

Wenn die Melodie die Stack-Nutzung eines Programms repräsentiert, könnte an jedem dieser fünf Stellen in der Programmausführung ein Stack-Überlauf auftreten. Mit anderen Worten, die gleiche zugrundeliegende Rekonstruktion, die durch eine immer höhere Wiedergabe der Melodie dargestellt wird, kann sich auf fünf verschiedene Arten manifestieren. Die "Rekursion" in dieser Analogie war ziemlich schnell, nur acht Takte bevor sich die Schleife wiederholte. In der Praxis kann die Schleife ziemlich lang sein, was zu Dutzenden potenzieller Punkte führt, an denen sich der Stapelüberlauf manifestieren kann. 

Wenn Sie mit einem Stack-Überlauf konfrontiert sind, möchten Sie die Spitze des Stacks ignorieren, da sich dies nur auf die bestimmte Note konzentriert, die Ihren Stimmbereich überschritten hat. Sie möchten wirklich die gesamte Melodie finden, da dies bei allen Stapelüberläufen mit der gleichen Grundursache üblich ist. 

13
Michael Myers

Möglicherweise möchten Sie prüfen, ob die Option "-Xss" von Ihrer JVM unterstützt wird. Wenn dies der Fall ist, können Sie versuchen, den Wert auf 512 KB einzustellen (der Standardwert ist 256 KB unter 32-Bit-Windows und Unix) und ob das alles bewirkt (außer dass Sie länger sitzen bleiben, bis Ihre StackOverflowException). Beachten Sie, dass dies eine Einstellung für jeden Thread ist. Wenn Sie also viele Threads ausführen, möchten Sie möglicherweise auch die Heap-Einstellungen aufstocken.

7
TMN

Die richtige Antwort ist die bereits gegebene. Sie haben wahrscheinlich entweder a) einen Fehler in Ihrem Code, der zu einer unendlichen Rekursion führt, die in der Regel leicht zu diagnostizieren und zu beheben ist, oder b) Code haben, der zu sehr tiefen Rekursionen führen kann, z. B. rekursives Durchlaufen eines unausgeglichenen Binärbaums. In letzterem Fall müssen Sie Ihren Code ändern, um die Informationen auf dem Stack nicht zuzuordnen (d. H. Nicht erneut zu rekursieren), sondern stattdessen im Heap. 

Bei einer asymmetrischen Baumdurchquerung können Sie beispielsweise die Knoten, die überarbeitet werden müssen, in einer Stack-Datenstruktur speichern. Bei einem Durchlauf in der richtigen Reihenfolge würden Sie die linken Zweige nach unten durchlaufen, indem Sie jeden Knoten während Ihres Besuchs schieben, bis Sie auf ein Blatt treffen, das Sie bearbeiten würden. Dann ziehen Sie einen Knoten aus dem Stapel, verarbeiten ihn und starten Ihre Schleife mit dem right child (indem Sie einfach Ihre Schleifenvariable auf den rechten Knoten setzen.) Dies erfordert eine konstante Stapelmenge, indem Sie alles, was sich auf dem Stack befand, auf den Heap in der Stack-Datenstruktur verschieben. Heap ist normalerweise viel reichlicher als Stack.

Als etwas, das normalerweise eine äußerst schlechte Idee ist, aber in Fällen, in denen der Speicherverbrauch extrem eingeschränkt ist, können Sie die Zeigerumkehr verwenden. Bei dieser Technik kodieren Sie den Stapel in die Struktur, die Sie durchlaufen, und wenn Sie die Verknüpfungen, die Sie durchlaufen, wiederverwenden, können Sie dies ohne oder mit wesentlich weniger zusätzlichem Speicher tun. Anhand des obigen Beispiels müssen wir beim Schleifen keine Knoten schieben, sondern uns an unser unmittelbares übergeordnetes Element erinnern. Bei jeder Iteration setzen wir die Verknüpfung, die wir durchlaufen haben, auf das aktuelle übergeordnete Element und dann das aktuelle übergeordnete Element auf den Knoten, den wir verlassen. Wenn wir zu einem Blatt kommen, verarbeiten wir es, dann gehen wir zu unseren Eltern und dann haben wir ein Rätsel. Wir wissen nicht, ob Sie den linken Zweig korrigieren, diesen Knoten verarbeiten und mit dem rechten Zweig fortfahren oder den rechten Zweig korrigieren und zu unserem übergeordneten Element wechseln möchten. Daher müssen wir während der Iteration ein zusätzliches bisschen Information zuweisen. In der Regel wird das Bit für Realisierungen dieser Technik im Zeiger selbst gespeichert, was zu keinem zusätzlichen Speicher und insgesamt zu einem konstanten Speicher führt. Dies ist keine Option in Java, aber es kann möglich sein, dieses Bit in Feldern wegzulassen, die für andere Dinge verwendet werden. Im schlimmsten Fall ist dies immer noch eine 32- oder 64-fache Verringerung des Speicherbedarfs. Natürlich ist dieser Algorithmus mit völlig verwirrenden Ergebnissen extrem leicht zu verwechseln und würde zu einem Chaos in der Parallelität führen. Der Alptraum der Wartung lohnt sich also fast nie, es sei denn, die Zuweisung von Speicher ist unhaltbar. Das typische Beispiel ist ein Speicherbereiniger, bei dem solche Algorithmen üblich sind.

Worüber ich wirklich reden wollte, ist, wann Sie den StackOverflowError behandeln möchten. Nämlich für die Anrufbeseitigung in der JVM. Ein Ansatz besteht darin, einen Trampolinstil zu verwenden, bei dem Sie anstelle eines Abrufaufrufs ein null-Prozedurobjekt zurückgeben, oder wenn Sie nur einen Wert zurückgeben, den Sie zurückgeben. [Anmerkung: Dies erfordert einige Mittel, um zu sagen, dass eine Funktion entweder A oder B zurückgibt. In Java ist dies wahrscheinlich der einfachste Weg, einen Typ normal zurückzugeben und den anderen als Ausnahme zu werfen.] Dann, wenn Sie eine Methode aufrufen, werden Sie Sie müssen eine while-Schleife ausführen, um die nullary-Prozeduren aufzurufen (die selbst entweder eine nullary-Prozedur oder einen Wert zurückgeben), bis Sie einen Wert erhalten. Eine Endlosschleife wird zu einer while-Schleife, die ständig Prozedurobjekte erzwingt, die Prozedurobjekte zurückgeben. Der Vorteil des Trampolin-Stils besteht darin, dass nur ein konstanter Faktor mehr Stack verwendet wird als bei einer Implementierung, bei der alle Tail-Aufrufe ordnungsgemäß eliminiert wurden. Es wird der normale Java-Stack für Nicht-Tail-Aufrufe verwendet Code mit einem (langwierigen) konstanten Faktor. Der Nachteil ist, dass Sie bei jedem Methodenaufruf ein Objekt zuordnen (das sofort zu Müll wird), und der Verbrauch dieser Objekte erfordert mehrere indirekte Aufrufe pro Tail-Aufruf.Ideal wäre es, diese nullary-Prozeduren oder gar nichts anderes überhaupt zuzuordnen. Dies ist genau das, was die Beseitigung der Rückrufe bewirken würde. Wenn wir jedoch mit dem arbeiten, was Java bietet, können wir den Code normal ausführen und diese null-Prozeduren nur ausführen, wenn der Stack leer ist. Jetzt ordnen wir diese nutzlosen Frames immer noch zu, aber wir tun dies auf dem Stack und nicht auf dem Heap und heben sie für die Aufteilung auf. Außerdem sind unsere Aufrufe normale direkte Java-Aufrufe. Der einfachste Weg, diese Umwandlung zu beschreiben, besteht darin, zunächst alle Multi-Call-Statement-Methoden in Methoden mit zwei Call-Anweisungen umzuschreiben, d. H. Fgh () {f (); G(); h (); } wird zu fgh () {f (); gh (); } und gh () {g (); h (); }. Der Einfachheit halber gehe ich davon aus, dass alle Methoden in einem Tail Call enden, der arrangiert werden kann, indem der Rest einer Methode in eine separate Methode gepackt wird. In der Praxis möchten Sie diese jedoch direkt behandeln. Nach diesen Transformationen gibt es drei Fälle: Entweder hat eine Methode keine Aufrufe, in diesem Fall gibt es nichts zu tun, oder es gibt einen Aufruf (Tail). In diesem Fall wickeln wir sie in einen Try-Catch-Block auf dieselbe Weise ein, in der wir dies tun werden der Schlussruf in dem Fall mit zwei Anrufen. Schließlich kann es zwei Aufrufe geben, einen Nicht-Tail-Aufruf und einen Tail-Aufruf. In diesem Fall wenden wir die folgende, durch ein Beispiel veranschaulichte Transformation an (mithilfe der Lambda-Notation von C #, die leicht durch eine anonyme innere Klasse mit etwas Wachstum ersetzt werden kann):.

// top-level handler Action tlh(Action act) { return () => { while(true) { try { act(); break; } catch(Bounce e) { tlh(() => e.run())(); } } } } gh() { try { g(); } catch(Bounce e) { throw new Bounce(tlh(() => { e.run(); try { h(); } catch(StackOverflowError e) { throw new Bounce(tlh(() => h()); } }); } try { h(); } catch(StackOverflowError e) { throw new Bounce(tlh(() => h())); } }

Das größte Problem bei diesem Ansatz besteht darin, dass Sie wahrscheinlich normale Java-Methoden aufrufen möchten, und Sie haben möglicherweise willkürlich wenig Stapelspeicherplatz zur Verfügung, so dass sie über ausreichend Speicherplatz verfügen, aber nicht fertig sind Mitte. Hierfür gibt es mindestens zwei Lösungen. Die erste besteht darin, alle diese Arbeiten an einen separaten Thread zu senden, der über einen eigenen Stapel verfügt. Dies ist ziemlich effektiv und ziemlich einfach und führt keine Parallelität ein (es sei denn, Sie möchten dies.) Eine weitere Option besteht darin, den Stack vor dem Aufruf einer normalen Java-Methode absichtlich aufzuwickeln, indem Sie einfach einen StackOverflowError direkt vor sich werfen. Wenn es nach dem Fortsetzen noch nicht mehr genügend Speicherplatz zur Verfügung steht, wurden Sie zunächst mit der Maus verschraubt.

Ähnlich kann man auch Just-in-Time-Fortsetzungen machen. Leider ist diese Umwandlung in Java nicht von Hand zu ertragen und ist wahrscheinlich für Sprachen wie C # oder Scala grenzwertig. Transformationen wie diese werden in der Regel von Sprachen durchgeführt, die auf die JVM abzielen, und nicht von Personen.

A similar thing can be done to make continuations just-in-time too. Unfortunately, this transformation isn't really bearable to do by hand in Java, and is probably borderline for languages like C# or Scala. So, transformations like this tend to be done by languages that target the JVM and not by people.

4
Derek Elkins

Die meisten Chancen, StackOverflowError zu erhalten, bestehen in der Verwendung von [langen/unendlichen] Rekursionen in rekursiven Funktionen.

Sie können die Funktionsrekursion vermeiden, indem Sie Ihren Anwendungsentwurf so ändern, dass stapelbare Datenobjekte verwendet werden. Es gibt Codierungsmuster zum Konvertieren rekursiver Codes in iterative Codeblöcke. Schauen Sie sich unten die Antworten an:

Sie vermeiden also das Stapeln von Speicher für Java für Ihre rezessiven Funktionsaufrufe, indem Sie Ihre eigenen Datenstapel verwenden.

1

Ich denke, das geht nicht - oder es hängt zumindest von der verwendeten Jvm ab. Stapelüberlauf bedeutet, dass Sie keinen Platz zum Speichern lokaler Variablen und zur Rückgabe von Adressen haben. Wenn Ihr jvm eine Art Kompilierung durchführt, haben Sie auch den stackoverflow im jvm, und das bedeutet, dass Sie es nicht handhaben oder fangen können. Der jvm muss beendet werden.

Es könnte eine Möglichkeit geben, eine jvm zu erstellen, die ein solches Verhalten zulässt, dies wäre jedoch langsam.

Ich habe das Verhalten mit dem jvm nicht getestet, aber in .net kann der Stackoverflow einfach nicht verarbeitet werden. Sogar versuchen zu fangen hilft nicht. Da Java und .net auf demselben Konzept beruhen (virtuelle Maschinen mit JIT), vermute ich, dass sich Java genauso verhalten würde. Das Vorhandensein einer stackoverflow-Ausnahmebedingung in .NET legt nahe, dass es einige VMs gibt, durch die das Programm sie abfangen kann. Dies ist jedoch nicht der Fall.

1
Tobias Langner

Java.lang.Error Javadoc:

Ein Fehler ist eine Unterklasse von Throwable die auf ernsthafte Probleme hinweist, die eine vernünftige Anwendung nicht versuchen sollte . Die meisten dieser Fehler sind anormale Zustände. Der ThreadDeath-Fehler ist, obwohl er eine "normale" Bedingung ist, auch eine Unterklasse von Error, da die meisten Anwendungen nicht versuchen sollten, ihn abzufangen. Eine Methode ist nicht erforderlich, um in ihrer Throws-Klausel alle Unterklassen von Error zu deklarieren, die während ausgelöst werden Die Ausführung der Methode wird jedoch nicht abgefangen, da diese Fehler anormale Bedingungen sind, die niemals auftreten sollten.

Also nicht. Versuchen Sie herauszufinden, was in der Logik Ihres Codes falsch ist. Diese Ausnahme tritt sehr häufig aufgrund einer unendlichen Rekursion auf.

0
jpangamarca

Einfach, 

Sehen Sie sich die Stack-Trace-Ablaufverfolgung an, die der StackOverflowError erzeugt, damit Sie wissen, wo er in Ihrem Code vorkommt, und verwenden Sie ihn, um herauszufinden, wie Sie den Code umschreiben, damit er sich nicht selbst rekursiv aufruft (die wahrscheinliche Ursache für den Fehler). Das passiert wieder.

StackOverflowErrors müssen nicht über eine try ... catch-Klausel behandelt werden, sondern weisen auf einen grundlegenden Fehler in der Logik Ihres Codes hin, der von Ihnen behoben werden muss.

0
Avrom

in manchen Fällen können Sie stackoverflowerror nicht fangen. Wenn Sie es versuchen, werden Sie auf ein neues stoßen. weil ist Java vm. Es ist gut, rekursive Codeblöcke zu finden, wie Andrew Bullock sagte .

0
ferhatozkanun

Die Stack-Ablaufverfolgung sollte die Art des Problems anzeigen. Beim Lesen der Stack-Ablaufverfolgung sollte es eine offensichtliche Schleife geben. 

Wenn es sich nicht um einen Fehler handelt, müssen Sie einen Zähler oder einen anderen Mechanismus hinzufügen, um die Rekursion zu stoppen, bevor die Rekursion so tief geht, dass ein Stapelüberlauf verursacht wird. 

Ein Beispiel dafür könnte sein, wenn Sie in einem DOM-Modell mit rekursiven Aufrufen verschachteltes XML verwenden und das XML so geschachtelt ist, dass es zu einem Stapelüberlauf mit Ihren verschachtelten Aufrufen kommt (unwahrscheinlich, aber möglich). Dies müsste jedoch ziemlich tief verschachtelt sein, um einen Stapelüberlauf zu verursachen.

0
Neal Maloney

Wie von vielen in diesem Thread erwähnt, ist der häufigste Grund ein rekursiver Methodenaufruf, der nicht beendet wird. Vermeiden Sie nach Möglichkeit den Stack-Überlauf und wenn Sie dies beim Testen tun, sollten Sie dies in den meisten Fällen als schwerwiegenden Fehler betrachten. In einigen Fällen können Sie die Thread-Stackgröße in Java so konfigurieren, dass sie für bestimmte Situationen größer ist (große Datenmengen werden im lokalen Stackspeicher verwaltet, lange rekursive Aufrufe). Dies erhöht jedoch den gesamten Speicherbedarf des Speichers, was zu Problemen bei der Anzahl führen kann der in der VM verfügbaren Threads. Wenn Sie diese Ausnahme erhalten, sollten im Allgemeinen der Thread und alle lokalen Daten zu diesem Thread als Toast betrachtet und nicht verwendet werden (dh als verdächtig und möglicherweise beschädigt). 

0
VHF