it-swarm.com.de

Was verursacht eigentlich einen Stapelüberlauffehler?

Ich habe überall gesucht und kann keine solide Antwort finden. Laut Dokumentation löst Java) unter folgenden Umständen einen Java.lang.StackOverflowError Fehler aus:

Wird ausgelöst, wenn ein Stapelüberlauf auftritt, weil eine Anwendung zu tief rekursiv ist.

Dies wirft jedoch zwei Fragen auf:

  • Gibt es keine anderen Möglichkeiten für einen Stapelüberlauf, nicht nur durch Rekursion?
  • Tritt der StackOverflowError auf, bevor die JVM den Stack tatsächlich überläuft, oder erst danach?

Um auf die zweite Frage näher einzugehen:

Wenn Java den StackOverflowError auslöst, können Sie sicher annehmen, dass der Stapel nicht in den Heap geschrieben wurde? Wenn Sie die Größe des Stapels oder des Heaps bei einem Versuch/Fang einer Funktion verringern, die a auslöst Stapelüberlauf, können Sie weiterarbeiten? Ist dies irgendwo dokumentiert?

Antworten, die ich nicht suche:

  • Ein StackOverflow tritt aufgrund einer schlechten Rekursion auf.
  • Ein StackOverflow tritt auf, wenn der Heap auf den Stack trifft.
234
retrohacker

Anscheinend denken Sie, dass ein Stackoverflow-Fehler einer Pufferüberlauf-Ausnahme in nativen Programmen gleicht, wenn das Risiko besteht, in den Speicher zu schreiben, der nicht für den Puffer reserviert wurde, und daher einige zu beschädigen andere Speicherplätze. Das ist überhaupt nicht der Fall.

JVM verfügt über einen bestimmten Speicher, der jedem Stapel jedes Threads zugewiesen ist. Wenn beim Aufrufen einer Methode versucht wird, diesen Speicher zu füllen, gibt JVM einen Fehler aus. Genau wie beim Versuch, bei Index N eines Arrays der Länge N zu schreiben. Es kann keine Speicherbeschädigung auftreten. Der Stack kann nicht in den Heap schreiben.

Ein StackOverflowError ist für den Stack gleichbedeutend mit einem OutOfMemoryError für den Heap: Er signalisiert lediglich, dass kein Speicher mehr verfügbar ist.

Beschreibung von Virtual Machine-Fehlern (§6.3)

StackOverflowError : Der Implementierung der Java Virtual Machine ist der Stapelspeicherplatz für einen Thread ausgegangen, normalerweise, weil der Thread ausgeführt wird eine unbegrenzte Anzahl von rekursiven Aufrufen infolge eines Fehlers im ausführenden Programm.

195
JB Nizet

Gibt es keine anderen Möglichkeiten für einen Stapelüberlauf, nicht nur durch Rekursion?

Sicher. Rufe einfach weiterhin Methoden auf, ohne jemals zurückzukehren. Sie benötigen jedoch viele Methoden, sofern Sie keine Rekursion zulassen. Eigentlich macht es keinen Unterschied: Ein Stack-Frame ist ein Stack-Frame, ob es sich um eine rekursive Methode handelt oder nicht.

Die Antwort auf Ihre zweite Frage lautet: Der Stapelüberlauf wird erkannt, wenn die JVM versucht, den Stapelrahmen für den nächsten Aufruf zuzuweisen, und feststellt, dass dies nicht möglich ist. Es wird also nichts überschrieben.

52
Ingo

Gibt es keine anderen Möglichkeiten für einen Stapelüberlauf, nicht nur durch Rekursion?

Herausforderung angenommen :) StackOverflowError ohne Rekursion (Herausforderung fehlgeschlagen, siehe Kommentare):

public class Test
{
    final static int CALLS = 710;

    public static void main(String[] args)
    {
        final Functor[] functors = new Functor[CALLS];
        for (int i = 0; i < CALLS; i++)
        {
            final int finalInt = i;
            functors[i] = new Functor()
            {
                @Override
                public void fun()
                {
                    System.out.print(finalInt + " ");
                    if (finalInt != CALLS - 1)
                    {
                        functors[finalInt + 1].fun();
                    }
                }
            };
        }
        // Let's get ready to ruuuuuuumble!
        functors[0].fun(); // Sorry, couldn't resist to not comment in such moment. 
    }

    interface Functor
    {
        void fun();
    }
}

Kompiliere mit Standard javac Test.Java Und starte mit Java -Xss104k Test 2> out. Danach sagt Ihnen more out:

Exception in thread "main" Java.lang.StackOverflowError

Zweiter Versuch.

Jetzt ist die Idee noch einfacher. Primitive in Java können auf dem Stack gespeichert werden. Deklarieren wir also viele Doubles wie double a1,a2,a3.... Dieses Skript kann schreiben, kompilieren und ausführen den Code) für uns:

#!/bin/sh

VARIABLES=4000
NAME=Test
FILE=$NAME.Java
SOURCE="public class $NAME{public static void main(String[] args){double "
for i in $(seq 1 $VARIABLES);
do
    SOURCE=$SOURCE"a$i,"
done
SOURCE=$SOURCE"b=0;System.out.println(b);}}"
echo $SOURCE > $FILE
javac $FILE
Java -Xss104k $NAME

Und ... ich habe etwas Unerwartetes:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f4822f9d501, pid=4988, tid=139947823249152
#
# JRE version: 6.0_27-b27
# Java VM: OpenJDK 64-Bit Server VM (20.0-b12 mixed mode linux-AMD64 compressed oops)
# Derivative: IcedTea6 1.12.6
# Distribution: Ubuntu 10.04.1 LTS, package 6b27-1.12.6-1ubuntu0.10.04.2
# Problematic frame:
# V  [libjvm.so+0x4ce501]  JavaThread::last_frame()+0xa1
#
# An error report file with more information is saved as:
# /home/adam/Desktop/test/hs_err_pid4988.log
#
# If you would like to submit a bug report, please include
# instructions how to reproduce the bug and visit:
#   https://bugs.launchpad.net/ubuntu/+source/openjdk-6/
#
Aborted

Es ist 100% repetitiv. Dies hängt mit Ihrer zweiten Frage zusammen:

Tritt der StackOverflowError auf, bevor die JVM den Stack tatsächlich überläuft, oder erst danach?

Im Fall von OpenJDK 20.0-b12 können wir also sehen, dass JVM zuerst explodierte. Aber es scheint ein Fehler zu sein, vielleicht kann das jemand in Kommentaren bestätigen, da ich nicht sicher bin. Soll ich das melden? Vielleicht ist es bereits in einer neueren Version behoben ... Laut JVM-Spezifikationslink (angegeben durch JB Nizet in einem Kommentar) sollte JVM ein StackOverflowError werfen, nicht sterben:

Wenn für die Berechnung in einem Thread ein größerer Stapel Java= Virtual Machine als zulässig erforderlich ist, löst die virtuelle Maschine Java= StackOverflowError aus.


Dritter Versuch.

public class Test {
    Test test = new Test();

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

Wir wollen ein neues Test Objekt erstellen. Also wird sein (impliziter) Konstruktor aufgerufen. Kurz zuvor werden jedoch alle Member von Test initialisiert. Also wird Test test = new Test() zuerst ausgeführt ...

Wir wollen ein neues Test Objekt erstellen ...

Update: Pech, das ist Rekursion, ich habe eine Frage dazu gestellt hier .

27

Es gibt keine "StackOverFlowException". Was Sie meinen, ist "StackOverFlowError".

Ja, Sie können weiterarbeiten, wenn Sie es fangen, da der Stapel gelöscht wird, wenn Sie das tun, aber das wäre eine schlechte und hässliche Option.

Wann genau wird der Fehler geworfen? - Wenn Sie eine Methode aufrufen und die JVM prüft, ob genügend Arbeitsspeicher vorhanden ist, um dies zu tun. Natürlich wird der Fehler geworfen, wenn es nicht möglich ist.

  • Nein, das ist der einzige Weg, wie Sie diesen Fehler bekommen können: Ihren Stack voll zu bekommen. Aber nicht nur durch Rekursion, sondern auch durch Aufrufen von Methoden, die unendlich viele andere Methoden aufrufen. Es ist ein sehr spezifischer Fehler, also nein.
  • Es wird geworfen, bevor der Stapel voll ist, genau dann, wenn Sie es überprüfen. Wo würden Sie die Daten ablegen, wenn kein Speicherplatz verfügbar ist? Andere überschreiben? Naah.
3
GabrielBB

Die häufigste Ursache für StackOverFlowError ist eine zu tiefe oder unendliche Rekursion.

Zum Beispiel:

public int yourMethod(){
       yourMethod();//infinite recursion
}

In Java:

Es gibt two Bereiche im Speicher des Heaps und Stapels. Das stack memory wird verwendet, um lokale Variablen und Funktionsaufrufe zu speichern, während heap memory wird zum Speichern von Objekten in Java verwendet.

Wenn im Stack kein Speicher mehr zum Speichern von Funktionsaufrufen oder lokalen Variablen vorhanden ist, wirft JVM Java.lang.StackOverFlowError

während wenn es keinen Heap-Platz mehr zum Erstellen von Objekten gibt, wirft JVM Java.lang.OutOfMemoryError

3
Lazy Coder

Es gibt zwei Hauptorte, an denen Dinge in Java gespeichert werden können. Der erste ist der Heap, der für dynamisch zugewiesene Objekte verwendet wird. new.

Zusätzlich erhält jeder laufende Thread einen eigenen Stapel und eine diesem Stapel zugewiesene Speichermenge.

Wenn Sie eine Methode aufrufen, werden Daten in den Stack verschoben, um den Methodenaufruf, die übergebenen Parameter und die Zuweisung lokaler Variablen aufzuzeichnen. Eine Methode mit fünf lokalen Variablen und drei Parametern belegt mehr Stapelspeicher als eine void doStuff() -Methode ohne lokale Variablen.

Die Hauptvorteile des Stacks bestehen darin, dass keine Speicherfragmentierung auftritt, dass alle Elemente für einen Methodenaufruf oben im Stapel zugeordnet sind und dass die Rückkehr von Methoden einfach ist. Wenn Sie von einer Methode zurückkehren möchten, müssen Sie den Stapel nur zur vorherigen Methode zurückkehren. Legen Sie dann einen beliebigen Wert für den Rückgabewert fest, und fertig.

Da der Stack eine feste Größe pro Thread hat (beachten Sie, dass für die Java Spec keine feste Größe erforderlich ist, die meisten JVM-Implementierungen zum Zeitpunkt des Schreibens jedoch eine feste Größe verwenden) und der Speicherplatz aktiviert ist Der Stack wird immer dann benötigt, wenn Sie einen Methodenaufruf ausführen. Hoffentlich sollte jetzt klar sein, warum er nicht mehr ausgeführt werden kann und warum er nicht mehr ausgeführt werden kann. Es gibt keine feste Anzahl von Methodenaufrufen, es gibt nichts Spezifisches an der Rekursion. Sie erhalten die Ausnahme, die Sie versuchen, eine Methode aufzurufen, und es ist nicht genügend Speicher vorhanden.

Natürlich ist die Größe der Stapel so hoch eingestellt, dass es im normalen Code sehr unwahrscheinlich ist, dass sie auftreten. In rekursivem Code kann es jedoch recht einfach sein, in große Tiefen zurückzukehren, und an diesem Punkt tritt dieser Fehler auf.

3
Tim B

StackOverflowError tritt auf, weil eine Anwendung zu häufig rekursiv vorkommt (Dies ist keine von Ihnen erwartete Antwort).

Nun, andere Dinge, die mit StackOverflowError passieren, sind, Methoden von Methoden aufzurufen, bis Sie StackOverflowError erhalten, aber niemand kann programmieren, um StackOverflowError zu erhalten, und selbst wenn diese Programmierer dies tun, dann tun sie dies nicht den Kodierungsstandards für zyklomatische Übereinstimmung folgen, die jeder Programmierer beim Programmieren verstehen muss. Ein solcher Grund für 'StackOverflowError' benötigt viel Zeit, um ihn zu beheben.

Die unwissentliche Codierung einer oder zweier Zeilen, die StackOverflowError verursacht, ist jedoch verständlich, und JVM löst dies aus, und wir können es sofort korrigieren. hier ist meine Antwort mit Bild für eine andere Frage.

2
AmitG

In c # können Sie einen Stapelüberlauf auf andere Weise erzielen, indem Sie die Objekteigenschaften falsch definieren. Beispielsweise :

private double hours;

public double Hours
        {
            get { return Hours; }
            set { Hours = value; }
        }

Wie Sie sehen, wird dies für immer Stunden mit einem Großbuchstaben H zurückgeben, der an sich Stunden und so weiter zurückgibt.

Ein Stapelüberlauf tritt häufig auch auf, wenn nicht genügend Arbeitsspeicher zur Verfügung steht oder wenn verwaltete Sprachen verwendet werden, da Ihr Sprachmanager (CLR, JRE) feststellt, dass Ihr Code in einer Endlosschleife steckt.

Ein StackOverflow tritt auf, wenn ein Funktionsaufruf erfolgt und der Stack voll ist.

Genau wie eine ArrayOutOfBoundException. Es kann nichts verderben, tatsächlich ist es sehr gut möglich, es zu fangen und sich davon zu erholen.

Dies ist normalerweise das Ergebnis einer unkontrollierten Rekursion, kann aber auch durch einen einfachen Aufruf eines sehr tiefen Stapels von Funktionen verursacht werden.

0
njzk2