it-swarm.com.de

Was ist ein StackOverflowError?

Was ist ein StackOverflowError, was verursacht es und wie soll ich damit umgehen?

403
Ziggy

Parameter und lokale Variablen werden auf dem Stapel zugewiesen (bei Referenztypen befindet sich das Objekt auf dem Heap, und eine Variable im Stapel verweist auf das Objekt auf dem Heap). Der Stapel befindet sich normalerweise am oberen Ende Ihres Adressraums und bewegt sich, wenn er aufgebraucht ist, in Richtung nteren des Adressraums (d. H. In Richtung Null).

Ihr Prozess hat auch einen Heap, der sich am Bottom Ende Ihres Prozesses befindet. Wenn Sie Speicher zuweisen, kann dieser Heap zum oberen Ende Ihres Adressraums hin wachsen. Wie Sie sehen können, kann der Haufen "kollidieren" mit dem Stapel (ein bisschen wie tektonische Platten !!!).

Die häufigste Ursache für einen Stapelüberlauf ist ein fehlerhafter rekursiver Aufruf. In der Regel wird dies verursacht, wenn Ihre rekursiven Funktionen nicht die richtige Beendigungsbedingung aufweisen und sich daher für immer selbst aufrufen. Wenn die Beendigungsbedingung in Ordnung ist, kann dies daran liegen, dass zu viele rekursive Aufrufe erforderlich sind, bevor sie erfüllt werden.

Mit der GUI-Programmierung ist es jedoch möglich, indirekte Rekursion zu generieren. Beispielsweise verarbeitet Ihre App möglicherweise Paint-Nachrichten und ruft während der Verarbeitung möglicherweise eine Funktion auf, die das System veranlasst, eine weitere Paint-Nachricht zu senden. Hier haben Sie sich nicht explizit angerufen, aber die OS/VM hat es für Sie erledigt.

Um mit ihnen umzugehen, müssen Sie Ihren Code untersuchen. Wenn Sie Funktionen haben, die sich selbst aufrufen, prüfen Sie, ob Sie eine Beendigungsbedingung haben. Ist dies der Fall, prüfen Sie, ob Sie beim Aufruf der Funktion mindestens eines der Argumente geändert haben. Andernfalls wird die rekursiv aufgerufene Funktion nicht sichtbar geändert, und die Beendigungsbedingung ist unbrauchbar. Bedenken Sie auch, dass der Speicher Ihres Stacks knapp werden kann, bevor eine gültige Beendigungsbedingung erreicht wird. Stellen Sie daher sicher, dass Ihre Methode Eingabewerte verarbeiten kann, die rekursivere Aufrufe erfordern.

Wenn Sie keine offensichtlichen rekursiven Funktionen haben, prüfen Sie, ob Sie Bibliotheksfunktionen aufrufen, die indirekt dazu führen, dass Ihre Funktion aufgerufen wird (wie im impliziten Fall oben).

375
Sean

Um dies zu beschreiben, lassen Sie uns zunächst verstehen, wie lokale Variablen und Objekte gespeichert werden.

Lokale Variablen werden im Stack gespeichert: enter image description here

Wenn Sie sich das Bild ansehen, sollten Sie verstehen, wie die Dinge funktionieren.

Wenn ein Funktionsaufruf von einer Anwendung Java aufgerufen wird, wird ein Stapelrahmen auf dem Aufrufstapel zugewiesen. Der Stack-Frame enthält die Parameter der aufgerufenen Methode, ihre lokalen Parameter und die Rücksprungadresse der Methode. Die Rücksprungadresse gibt den Ausführungspunkt an, von dem aus die Programmausführung fortgesetzt werden soll, nachdem die aufgerufene Methode zurückgekehrt ist. Wenn kein Platz für einen neuen Stack-Frame vorhanden ist, wird StackOverflowError von der Java Virtual Machine (JVM) ausgegeben.

Der häufigste Fall, in dem der Stapel einer Java -Anwendung möglicherweise erschöpft sein kann, ist die Rekursion. In der Rekursion ruft sich eine Methode während ihrer Ausführung auf. Rekursion wird als leistungsstarke Allzweck-Programmiertechnik angesehen, muss jedoch mit Vorsicht angewendet werden, um StackOverflowError zu vermeiden.

Ein Beispiel für das Werfen eines StackOverflowError ist unten dargestellt:

StackOverflowErrorExample.Java:

public class StackOverflowErrorExample {

    public static void recursivePrint(int num) {
        System.out.println("Number: " + num);

        if(num == 0)
            return;
        else
            recursivePrint(++num);
    }

    public static void main(String[] args) {
        StackOverflowErrorExample.recursivePrint(1);
    }
}

In diesem Beispiel definieren wir eine rekursive Methode mit dem Namen recursivePrint, die eine Ganzzahl ausgibt und sich dann selbst mit der nächsten aufeinanderfolgenden Ganzzahl als Argument aufruft. Die Rekursion endet, bis wir 0 als Parameter übergeben. In unserem Beispiel haben wir jedoch den Parameter von 1 und seine aufsteigenden Follower übergeben, sodass die Rekursion niemals beendet wird.

Eine Beispielausführung unter Verwendung des Flags -Xss1M, mit dem die Größe des Thread-Stapels auf 1 MB festgelegt wird, ist im Folgenden dargestellt:

Number: 1
Number: 2
Number: 3
...
Number: 6262
Number: 6263
Number: 6264
Number: 6265
Number: 6266
Exception in thread "main" Java.lang.StackOverflowError
        at Java.io.PrintStream.write(PrintStream.Java:480)
        at Sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.Java:221)
        at Sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.Java:291)
        at Sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.Java:104)
        at Java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.Java:185)
        at Java.io.PrintStream.write(PrintStream.Java:527)
        at Java.io.PrintStream.print(PrintStream.Java:669)
        at Java.io.PrintStream.println(PrintStream.Java:806)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.Java:4)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.Java:9)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.Java:9)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.Java:9)
        ...

Abhängig von der anfänglichen Konfiguration der JVM können die Ergebnisse abweichen, aber möglicherweise wird StackOverflowError ausgegeben. Dieses Beispiel ist ein sehr gutes Beispiel dafür, wie Rekursion Probleme verursachen kann, wenn sie nicht mit Vorsicht implementiert wird.

Umgang mit dem StackOverflowError

  1. Die einfachste Lösung besteht darin, die Stapelverfolgung sorgfältig zu untersuchen und das sich wiederholende Muster der Zeilennummern zu erkennen. Diese Zeilennummern geben den Code an, der rekursiv aufgerufen wird. Sobald Sie diese Zeilen erkannt haben, müssen Sie Ihren Code sorgfältig untersuchen und verstehen, warum die Rekursion niemals beendet wird.

  2. Wenn Sie überprüft haben, dass die Rekursion korrekt implementiert ist, können Sie den Stapel vergrößern, um eine größere Anzahl von Aufrufen zuzulassen. Abhängig von der installierten Java Virtual Machine (JVM) kann die Standardgröße des Thread-Stacks 512 KB oder 1 MB betragen. Sie können den Thread-Stapel mit dem Flag -Xss vergrößern. Dieses Flag kann entweder über die Projektkonfiguration oder über die Befehlszeile angegeben werden. Das Format des Arguments -Xss lautet: -Xss<size>[g|G|m|M|k|K]

94
Varun

Wenn Sie eine Funktion haben wie:

int foo()
{
    // more stuff
    foo();
}

Dann ruft sich foo () immer weiter auf und wird immer tiefer. Wenn der Platz, der zum Verfolgen der Funktionen, in denen Sie sich befinden, verwendet wird, voll ist, wird der Stapelüberlauffehler angezeigt.

62
Khoth

Stapelüberlauf bedeutet genau das: Ein Stapel läuft über. Normalerweise enthält das Programm einen Stapel mit Variablen und Adressen für den lokalen Bereich, die zurückgegeben werden sollen, wenn die Ausführung einer Routine endet. Dieser Stapel ist in der Regel ein fester Speicherbereich, daher ist die Anzahl der Werte begrenzt.

Wenn der Stapel leer ist, können Sie nicht platzieren. Andernfalls wird ein Stapelunterlauffehler angezeigt.

Wenn der Stapel voll ist, können Sie nicht pushen. Andernfalls wird ein Stapelüberlauffehler angezeigt.

Ein Stapelüberlauf tritt also auf, wenn Sie dem Stapel zu viel zuweisen. Zum Beispiel in der erwähnten Rekursion.

Einige Implementierungen optimieren einige Formen von Rekursionen. Insbesondere Schwanzrekursion. Schwanzrekursive Routinen sind Routinen, bei denen der rekursive Aufruf als letztes angezeigt wird, was die Routine tut. Ein solcher Routineaufruf wird einfach zu einem Sprung reduziert.

Einige Implementierungen implementieren sogar ihre eigenen Stapel für die Rekursion, sodass die Rekursion fortgesetzt werden kann, bis dem System der Speicher ausgeht.

Das Einfachste, was Sie versuchen könnten, wäre, Ihren Stapel zu vergrößern, wenn Sie können. Wenn Sie dies jedoch nicht tun können, ist es am zweitbesten, zu prüfen, ob es etwas gibt, das eindeutig den Stapelüberlauf verursacht. Probieren Sie es aus, indem Sie vor und nach dem Aufruf etwas in die Routine drucken. Dies hilft Ihnen, die fehlerhafte Routine herauszufinden.

24
Cheery

Ein Stapelüberlauf wird normalerweise durch zu tiefes Verschachteln von Funktionsaufrufen (besonders einfach bei Verwendung von Rekursion, d. H. Einer Funktion, die sich selbst aufruft) oder durch Zuweisen einer großen Menge an Speicher auf dem Stapel aufgerufen, wenn die Verwendung des Heap-Speichers angemessener wäre.

8
Greg

Wie Sie sagen, müssen Sie Code anzeigen. :-)

Ein Stapelüberlauffehler tritt normalerweise auf, wenn Ihre Funktionsaufrufe zu tief verschachtelt sind. Im Thread Stack Overflow Code Golf finden Sie einige Beispiele, wie dies geschieht (obwohl bei dieser Frage die Antworten absichtlich einen Stapelüberlauf verursachen).

6

StackOverflowError ist auf dem Stapel wie OutOfMemoryError auf dem Haufen.

Ungebundene rekursive Aufrufe führen dazu, dass Stapelspeicherplatz verbraucht wird.

Das folgende Beispiel erzeugt StackOverflowError:

class  StackOverflowDemo
{
    public static void unboundedRecursiveCall() {
     unboundedRecursiveCall();
    }

    public static void main(String[] args) 
    {
        unboundedRecursiveCall();
    }
}

StackOverflowError ist vermeidbar, wenn rekursive Aufrufe beschränkt sind, um zu verhindern, dass die Gesamtsumme der unvollständigen Speicheraufrufe (in Byte) die Stapelgröße (in Byte) überschreitet.

5
Vikram

Die häufigste Ursache für Stapelüberläufe ist eine zu tiefe oder unendliche Rekursion . Wenn dies Ihr Problem ist, kann dieses Tutorial zu Java Recursion helfen, das Problem zu verstehen.

5
splattne

Ein StackOverflowError ist ein Laufzeitfehler in Java.

Es wird ausgelöst, wenn der von JVM zugewiesene Call-Stack-Speicher überschritten wird.

Ein häufiger Fall, in dem StackOverflowError ausgelöst wird, liegt vor, wenn der Aufrufstapel aufgrund einer zu tiefen oder unendlichen Rekursion überschritten wird.

Beispiel:

public class Factorial {
    public static int factorial(int n){
        if(n == 1){
            return 1;
        }
        else{
            return n * factorial(n-1);
        }
    }

    public static void main(String[] args){
        System.out.println("Main method started");
        int result = Factorial.factorial(-1);
        System.out.println("Factorial ==>"+result);
        System.out.println("Main method ended");
    }
}

Stack-Trace:

Main method started
Exception in thread "main" Java.lang.StackOverflowError
at com.program.stackoverflow.Factorial.factorial(Factorial.Java:9)
at com.program.stackoverflow.Factorial.factorial(Factorial.Java:9)
at com.program.stackoverflow.Factorial.factorial(Factorial.Java:9)

In dem obigen Fall kann dies durch programmatische Änderungen vermieden werden. Wenn die Programmlogik jedoch korrekt ist und sie weiterhin auftritt, muss die Stapelgröße erhöht werden.

3
Rahul Sah

Hier ist ein Beispiel eines rekursiven Algorithmus zum Umkehren einer einfach verknüpften Liste. Auf einem Laptop mit der folgenden Spezifikation (4G-Speicher, Intel Core i5 2.3GHz-CPU, 64-Bit-Windows 7) tritt bei einer verknüpften Liste mit einer Größe von nahezu 10.000 ein StackOverflow-Fehler auf.

Mein Punkt ist, dass wir die Rekursion mit Bedacht anwenden sollten, immer unter Berücksichtigung des Maßstabs des Systems. Oft kann die Rekursion in ein iteratives Programm konvertiert werden, das besser skaliert. (Eine iterative Version desselben Algorithmus befindet sich am Ende der Seite. Sie kehrt eine einfach verknüpfte Liste der Größe 1 Million in 9 Millisekunden um.)

    private static LinkedListNode doReverseRecursively(LinkedListNode x, LinkedListNode first){

    LinkedListNode second = first.next;

    first.next = x;

    if(second != null){
        return doReverseRecursively(first, second);
    }else{
        return first;
    }
}

public static LinkedListNode reverseRecursively(LinkedListNode head){
    return doReverseRecursively(null, head);
}

Iterative Version desselben Algorithmus:

    public static LinkedListNode reverseIteratively(LinkedListNode head){
    return doReverseIteratively(null, head);
}   

private static LinkedListNode doReverseIteratively(LinkedListNode x, LinkedListNode first) {

    while (first != null) {
        LinkedListNode second = first.next;
        first.next = x;
        x = first;

        if (second == null) {
            break;
        } else {
            first = second;
        }
    }
    return first;
}


public static LinkedListNode reverseIteratively(LinkedListNode head){
    return doReverseIteratively(null, head);
}
3
Yiling

Hier ist ein Beispiel

public static void main(String[] args) {
    System.out.println(add5(1));
}

public static int add5(int a) {
    return add5(a) + 5;
}

Ein StackOverflowError ist im Grunde genommen, wenn Sie versuchen, etwas zu tun, das sich höchstwahrscheinlich von selbst aufruft und unendlich weitergeht (oder bis es einen StackOverflowError gibt).

add5(a) ruft sich selbst auf und ruft sich dann erneut auf und so weiter.

0
John S.

Der Begriff "Stack Overrun (Überlauf)" wird häufig verwendet, ist jedoch eine falsche Bezeichnung. Angriffe überlaufen nicht den Stapel, sondern Puffer auf dem Stapel.

- aus Vorlesungsfolien von Prof. Dr. Dieter Gollmann

0
Sergiu

Dies ist ein typischer Fall von Java.lang.StackOverflowError... Die Methode ruft sich selbst rekursiv auf, ohne doubleValue(), floatValue() usw. zu beenden.

Rational.Java

    public class Rational extends Number implements Comparable<Rational> {
        private int num;
        private int denom;

        public Rational(int num, int denom) {
            this.num = num;
            this.denom = denom;
        }

        public int compareTo(Rational r) {
            if ((num / denom) - (r.num / r.denom) > 0) {
                return +1;
            } else if ((num / denom) - (r.num / r.denom) < 0) {
                return -1;
            }
            return 0;
        }

        public Rational add(Rational r) {
            return new Rational(num + r.num, denom + r.denom);
        }

        public Rational sub(Rational r) {
            return new Rational(num - r.num, denom - r.denom);
        }

        public Rational mul(Rational r) {
            return new Rational(num * r.num, denom * r.denom);
        }

        public Rational div(Rational r) {
            return new Rational(num * r.denom, denom * r.num);
        }

        public int gcd(Rational r) {
            int i = 1;
            while (i != 0) {
                i = denom % r.denom;
                denom = r.denom;
                r.denom = i;
            }
            return denom;
        }

        public String toString() {
            String a = num + "/" + denom;
            return a;
        }

        public double doubleValue() {
            return (double) doubleValue();
        }

        public float floatValue() {
            return (float) floatValue();
        }

        public int intValue() {
            return (int) intValue();
        }

        public long longValue() {
            return (long) longValue();
        }
    }

Main.Java

    public class Main {

        public static void main(String[] args) {

            Rational a = new Rational(2, 4);
            Rational b = new Rational(2, 6);

            System.out.println(a + " + " + b + " = " + a.add(b));
            System.out.println(a + " - " + b + " = " + a.sub(b));
            System.out.println(a + " * " + b + " = " + a.mul(b));
            System.out.println(a + " / " + b + " = " + a.div(b));

            Rational[] arr = {new Rational(7, 1), new Rational(6, 1),
                    new Rational(5, 1), new Rational(4, 1),
                    new Rational(3, 1), new Rational(2, 1),
                    new Rational(1, 1), new Rational(1, 2),
                    new Rational(1, 3), new Rational(1, 4),
                    new Rational(1, 5), new Rational(1, 6),
                    new Rational(1, 7), new Rational(1, 8),
                    new Rational(1, 9), new Rational(0, 1)};

            selectSort(arr);

            for (int i = 0; i < arr.length - 1; ++i) {
                if (arr[i].compareTo(arr[i + 1]) > 0) {
                    System.exit(1);
                }
            }


            Number n = new Rational(3, 2);

            System.out.println(n.doubleValue());
            System.out.println(n.floatValue());
            System.out.println(n.intValue());
            System.out.println(n.longValue());
        }

        public static <T extends Comparable<? super T>> void selectSort(T[] array) {

            T temp;
            int mini;

            for (int i = 0; i < array.length - 1; ++i) {

                mini = i;

                for (int j = i + 1; j < array.length; ++j) {
                    if (array[j].compareTo(array[mini]) < 0) {
                        mini = j;
                    }
                }

                if (i != mini) {
                    temp = array[i];
                    array[i] = array[mini];
                    array[mini] = temp;
                }
            }
        }
    }

Ergebnis

    2/4 + 2/6 = 4/10
    Exception in thread "main" Java.lang.StackOverflowError
    2/4 - 2/6 = 0/-2
        at com.xetrasu.Rational.doubleValue(Rational.Java:64)
    2/4 * 2/6 = 4/24
        at com.xetrasu.Rational.doubleValue(Rational.Java:64)
    2/4 / 2/6 = 12/8
        at com.xetrasu.Rational.doubleValue(Rational.Java:64)
        at com.xetrasu.Rational.doubleValue(Rational.Java:64)
        at com.xetrasu.Rational.doubleValue(Rational.Java:64)
        at com.xetrasu.Rational.doubleValue(Rational.Java:64)
        at com.xetrasu.Rational.doubleValue(Rational.Java:64)

Hier ist der Quellcode von StackOverflowError in OpenJDK 7

0
user8389458