Die Stapelgröße in Java ist klein. Und manchmal, wie viele rekursive Aufrufe, sind Sie mit diesem Problem konfrontiert. Sie können Ihren Code per Schleife neu gestalten. Sie können allgemeine Entwurfsmuster finden, um es in dieser URL zu tun: jndanial.com/73
JNDanial
Ein nicht offensichtlicher Weg, um es zu bekommen: Fügen Sie die Zeile new Object() {{getClass().newInstance();}};einem statischen Kontext hinzu (z main. B. Methode). Funktioniert nicht im Instanzkontext (nur Würfe InstantiationException).
John McClane
Antworten:
408
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 dieses Objekt auf dem Heap). Der Stapel befindet sich normalerweise am oberen Ende Ihres Adressraums und bewegt sich, wenn er aufgebraucht ist, zum unteren Rand des Adressraums (dh gegen Null).
Ihr Prozess hat auch einen Haufen , der sich am unteren Ende Ihres Prozesses befindet. Wenn Sie Speicher zuweisen, kann dieser Heap zum oberen Ende Ihres Adressraums hin wachsen. Wie Sie sehen können, besteht die Möglichkeit, dass der Haufen mit dem Stapel "kollidiert" (ein bisschen wie bei tektonischen Platten !!!).
Die häufigste Ursache für einen Stapelüberlauf ist ein schlechter rekursiver Aufruf . In der Regel wird dies verursacht, wenn Ihre rekursiven Funktionen nicht die richtige Beendigungsbedingung haben und sich daher für immer selbst aufrufen. Wenn die Beendigungsbedingung in Ordnung ist, kann dies dadurch verursacht werden, dass zu viele rekursive Aufrufe erforderlich sind, bevor sie erfüllt werden.
Mit der GUI-Programmierung ist es jedoch möglich, eine indirekte Rekursion zu generieren . Beispielsweise verarbeitet Ihre App möglicherweise Malmeldungen und ruft während der Verarbeitung eine Funktion auf, die das System veranlasst, eine weitere Malmeldung zu senden. Hier haben Sie sich nicht explizit angerufen, aber das OS / VM hat es für Sie erledigt.
Um mit ihnen fertig zu werden, müssen Sie Ihren Code überprüfen. Wenn Sie Funktionen haben, die sich selbst aufrufen, überprüfen Sie, ob Sie eine Beendigungsbedingung haben. Wenn ja, überprüfen Sie, ob Sie beim Aufrufen der Funktion mindestens eines der Argumente geändert haben. Andernfalls wird die rekursiv aufgerufene Funktion nicht sichtbar geändert, und die Beendigungsbedingung ist unbrauchbar. Beachten Sie auch, dass Ihrem Stack-Speicher möglicherweise der Speicherplatz ausgeht, 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, überprüfen Sie, ob Sie Bibliotheksfunktionen aufrufen, die indirekt dazu führen, dass Ihre Funktion aufgerufen wird (wie im obigen impliziten Fall).
Originalplakat: Hey, das ist großartig. Die Rekursion ist also immer für Stapelüberläufe verantwortlich? Oder können auch andere Dinge für sie verantwortlich sein? Leider benutze ich eine Bibliothek ... aber keine, die ich verstehe.
Ziggy
4
Ha ha ha, also hier ist es: while (Punkte <100) {addMouseListeners (); moveball (); checkforcollision (); Pause (Geschwindigkeit);} Wow, fühle ich mich lahm, weil ich nicht bemerkt habe, dass ich am Ende eine Menge Maushörer haben würde ... Danke Jungs!
Ziggy
4
Nein, Stapelüberläufe können auch von Variablen herrühren, die zu groß sind, um sie dem Stapel zuzuweisen, wenn Sie den Wikipedia-Artikel unter en.wikipedia.org/wiki/Stack_overflow nachschlagen .
JB King
8
Es sollte darauf hingewiesen werden, dass es fast unmöglich ist, einen Stapelüberlauffehler zu "behandeln". In den meisten Umgebungen muss Code auf dem Stapel ausgeführt werden, um den Fehler zu beheben. Dies ist schwierig, wenn kein Stapelspeicher mehr vorhanden ist.
Hot Licks
3
@JB King: Gilt nicht wirklich für Java, wo nur primitive Typen und Referenzen auf dem Stapel gespeichert sind. Alle großen Dinge (Arrays und Objekte) sind auf dem Haufen.
Jcsahnwaldt sagt GoFundMonica
107
Um dies zu beschreiben, lassen Sie uns zunächst verstehen, wie lokale Variablen und Objekte gespeichert werden.
Lokale Variablen werden im Stapel gespeichert :
Wenn Sie sich das Bild ansehen, sollten Sie verstehen können, wie die Dinge funktionieren.
Wenn ein Funktionsaufruf von einer Java-Anwendung aufgerufen wird, wird dem Aufrufstapel ein Stapelrahmen zugewiesen. Der Stapelrahmen 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ückgegeben wurde. Wenn kein Platz für einen neuen Stapelrahmen vorhanden ist, StackOverflowErrorwird dieser von der Java Virtual Machine (JVM) ausgelöst.
Der häufigste Fall, der möglicherweise den Stapel einer Java-Anwendung erschöpfen kann, ist die Rekursion. Bei der Rekursion ruft sich eine Methode während ihrer Ausführung auf. Rekursion wird als leistungsstarke Allzweckprogrammiertechnik angesehen, muss jedoch mit Vorsicht angewendet werden, um dies zu vermeiden StackOverflowError.
Ein Beispiel für das Werfen von a StackOverflowErrorist unten dargestellt:
In diesem Beispiel definieren wir eine rekursive Methode, recursivePrintdie eine Ganzzahl druckt und sich dann selbst aufruft, wobei die nächste aufeinanderfolgende Ganzzahl ein Argument ist. Die Rekursion endet, bis wir 0als Parameter übergeben. In unserem Beispiel haben wir jedoch den Parameter von 1 und seinen zunehmenden Followern übergeben. Folglich wird die Rekursion niemals beendet.
Eine Beispielausführung unter Verwendung des -Xss1MFlags, das die Größe des Thread-Stapels auf 1 MB angibt, ist unten 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 ursprünglichen Konfiguration der JVM können die Ergebnisse unterschiedlich sein, aber eventuell StackOverflowErrorwerden sie geworfen. 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
Die einfachste Lösung besteht darin, die Stapelspur 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 prüfen und verstehen, warum die Rekursion niemals beendet wird.
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 zu ermöglichen. Abhängig von der installierten Java Virtual Machine (JVM) kann die Standardgröße des Thread-Stacks entweder 512 KB oder 1 MB betragen . Sie können die Thread-Stapelgröße mithilfe des -XssFlags erhöhen . Dieses Flag kann entweder über die Projektkonfiguration oder über die Befehlszeile angegeben werden. Das Format des
-XssArguments lautet:
-Xss<size>[g|G|m|M|k|K]
In einigen Java-Versionen scheint es einen Fehler zu geben, wenn Windows verwendet wird, bei dem das Argument -Xss nur für neue Threads
wirksam wird
65
Wenn Sie eine Funktion haben wie:
int foo()
{
// more stuff
foo();
}
Dann ruft sich foo () immer weiter auf und wird immer tiefer. Wenn der Speicherplatz, der zum Verfolgen der Funktionen verwendet wird, gefüllt ist, wird der Stapelüberlauffehler angezeigt.
Falsch. Ihre Funktion ist schwanzrekursiv. Die meisten kompilierten Sprachen verfügen über Optimierungen für die Schwanzrekursion. Dies bedeutet, dass sich die Rekursion zu einer einfachen Schleife reduziert und Sie auf einigen Systemen mit diesem Code niemals auf einen Stapelüberlauf stoßen.
Fröhlich
Fröhlich, welche nicht funktionierenden Sprachen unterstützen die Schwanzrekursion?
Horseyguy
@ Banister und einige Implementierungen von Javascript
Pacerier
@horseyguy Scala unterstützt die Schwanzrekursion.
Ajit K'sagar
Dies erfasst die Essenz dessen, was einen Stapelüberlauf erzeugen kann. Nett.
Pixel
24
Stapelüberlauf bedeutet genau das: Ein Stapel läuft über. Normalerweise enthält das Programm einen Stapel, der Variablen und Adressen für den lokalen Bereich enthält, an die zurückgegeben werden soll, wenn die Ausführung einer Routine endet. Dieser Stapel ist in der Regel ein fester Speicherbereich irgendwo im Speicher, daher ist er begrenzt, wie viel er Werte enthalten kann.
Wenn der Stapel leer ist, können Sie nicht platzen. Andernfalls wird ein Stapelunterlauffehler angezeigt.
Wenn der Stapel voll ist, können Sie nicht pushen. Andernfalls wird ein Stapelüberlauffehler angezeigt.
Der Stapelüberlauf wird also dort angezeigt, wo 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 eine Form von Routinen, bei denen der rekursive Aufruf als letzte Aufgabe der Routine angezeigt wird. Ein solcher Routineaufruf wird einfach in einen Sprung reduziert.
Einige Implementierungen gehen so weit, dass sie ihre eigenen Stapel für die Rekursion implementieren. Daher können sie die Rekursion fortsetzen, bis dem System der Speicher ausgeht.
Am einfachsten könnten Sie versuchen, die Stapelgröße zu erhöhen, 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.
In der Assembly ist ein Stapelunterlauf möglich (mehr Popping als Sie gepusht haben), in kompilierten Sprachen wäre dies jedoch nahezu unmöglich. Ich bin mir nicht sicher, ob Sie möglicherweise eine Implementierung von Cs alloca () finden, die negative Größen "unterstützt".
Score_Under
2
Stapelüberlauf bedeutet genau das: Ein Stapel läuft über. Normalerweise gibt es einen Stapel im Programm, der Variablen für den lokalen Bereich enthält -> Nein, jeder Thread hat einen eigenen Stapel, der Stapelrahmen für jeden Methodenaufruf enthält, der lokale Variablen enthält.
Koray Tugay
9
Ein Stapelüberlauf wird normalerweise aufgerufen, indem Funktionsaufrufe zu tief verschachtelt werden (besonders einfach bei Verwendung der Rekursion, dh einer Funktion, die sich selbst aufruft) oder indem eine große Menge an Speicher auf dem Stapel zugewiesen wird, wo die Verwendung des Heaps besser geeignet wäre.
Auch aus dem Originalplakat hier: Verschachtelungsfunktionen in was zu tief? Andere Funktionen? Und: Wie ordnet man dem Stapel oder Heap Speicher zu (da ich eines dieser Dinge eindeutig getan habe, ohne es zu wissen)?
Ziggy
@Ziggy: Ja, wenn eine Funktion eine andere Funktion aufruft, die noch eine andere Funktion aufruft usw. Nach vielen Ebenen hat Ihr Programm einen Stapelüberlauf. [fährt fort]
Chris Jester-Young
[... Fortsetzung] In Java können Sie den Speicher nicht direkt vom Stapel zuweisen (während dies in C möglich ist und dies dann zu beachten wäre), sodass dies wahrscheinlich nicht die Ursache ist. In Java stammen alle direkten Zuweisungen vom Heap, indem "new" verwendet wird.
Chris Jester-Young
@ ChrisJester-Young Stimmt es nicht, dass wenn ich 100 lokale Variablen in einer Methode habe, alles ausnahmslos auf dem Stapel liegt?
Pacerier
7
Wie Sie sagen, müssen Sie Code anzeigen. :-)
Ein Stapelüberlauffehler tritt normalerweise auf, wenn Ihre Funktionsaufrufe zu tief verschachtelt sind. Im Golf- Thread "Stapelüberlaufcode" finden Sie einige Beispiele dafür (obwohl bei dieser Frage die Antworten absichtlich einen Stapelüberlauf verursachen).
Ich würde gerne Code hinzufügen, aber da ich nicht weiß, was Stapelüberläufe verursacht, bin ich mir nicht sicher, welchen Code ich hinzufügen soll. Das Hinzufügen des gesamten Codes wäre lahm, nein?
Ziggy
Ist Ihr Projekt Open Source? Wenn ja, erstellen Sie einfach ein Sourceforge- oder Github-Konto und laden Sie Ihren gesamten Code dort hoch. :-)
Chris Jester-Young
Das klingt nach einer großartigen Idee, aber ich bin so ein Neuling, dass ich nicht einmal weiß, was ich hochladen müsste. Die Bibliothek, in die ich Klassen importiere, die ich erweitere usw., ist mir unbekannt. Oh Mann: schlechte Zeiten.
Ziggy
5
Die häufigste Ursache für Stapelüberläufe ist eine zu tiefe oder unendliche Rekursion . Wenn dies Ihr Problem ist, kann dieses Tutorial zur Java-Rekursion helfen, das Problem zu verstehen.
StackOverflowErrorist zum Stapel wie OutOfMemoryErrorzum Haufen.
Ungebundene rekursive Aufrufe führen dazu, dass der Stapelspeicher belegt wird.
Das folgende Beispiel ergibt StackOverflowError:
class StackOverflowDemo
{
public static void unboundedRecursiveCall() {
unboundedRecursiveCall();
}
public static void main(String[] args)
{
unboundedRecursiveCall();
}
}
StackOverflowError ist vermeidbar, wenn rekursive Aufrufe begrenzt sind, um zu verhindern, dass die Gesamtsumme unvollständiger speicherinterner Aufrufe (in Bytes) die Stapelgröße (in Bytes) überschreitet.
Hier ist ein Beispiel eines rekursiven Algorithmus zum Umkehren einer einfach verknüpften Liste. Auf einem Laptop mit der folgenden Spezifikation (4 G-Speicher, Intel Core i5 2,3-GHz-CPU, 64-Bit-Windows 7) tritt bei dieser Funktion ein StackOverflow-Fehler für eine verknüpfte Liste mit einer Größe nahe 10.000 auf.
Mein Punkt ist, dass wir die Rekursion mit Bedacht anwenden sollten, wobei wir immer die Größe des Systems berücksichtigen sollten. 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 mit einer Größe von 1 Million in 9 Millisekunden um.)
Ich denke, bei JVM spielt es keine Rolle, welche Spezifikation Ihr Laptop hat.
Kevin
3
A StackOverflowErrorist ein Laufzeitfehler in Java.
Es wird ausgelöst, wenn die von JVM zugewiesene Menge an Call-Stack-Speicher überschritten wird.
Ein häufiger Fall eines StackOverflowErrorWurfs ist, 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");
}
}
Stapelspur:
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)
Im obigen Fall kann dies durch programmatische Änderungen vermieden werden. Wenn die Programmlogik jedoch korrekt ist und dennoch auftritt, muss die Stapelgröße erhöht werden.
Dies ist ein typischer Fall von java.lang.StackOverflowError... Die Methode ruft sich rekursiv ohne Exit in auf doubleValue().floatValue() usw.
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)
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 selbst aufruft und unendlich weitergeht (oder bis es einen StackOverflowError gibt).
add5(a) ruft sich selbst an und ruft sich dann erneut an und so weiter.
Der Begriff "Stapelüberlauf (Überlauf)" wird häufig verwendet, ist jedoch eine falsche Bezeichnung. Angriffe überlaufen den Stapel nicht, sondern puffern auf dem Stapel.
new Object() {{getClass().newInstance();}};
einem statischen Kontext hinzu (zmain
. B. Methode). Funktioniert nicht im Instanzkontext (nur WürfeInstantiationException
).Antworten:
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 dieses Objekt auf dem Heap). Der Stapel befindet sich normalerweise am oberen Ende Ihres Adressraums und bewegt sich, wenn er aufgebraucht ist, zum unteren Rand des Adressraums (dh gegen Null).
Ihr Prozess hat auch einen Haufen , der sich am unteren Ende Ihres Prozesses befindet. Wenn Sie Speicher zuweisen, kann dieser Heap zum oberen Ende Ihres Adressraums hin wachsen. Wie Sie sehen können, besteht die Möglichkeit, dass der Haufen mit dem Stapel "kollidiert" (ein bisschen wie bei tektonischen Platten !!!).
Die häufigste Ursache für einen Stapelüberlauf ist ein schlechter rekursiver Aufruf . In der Regel wird dies verursacht, wenn Ihre rekursiven Funktionen nicht die richtige Beendigungsbedingung haben und sich daher für immer selbst aufrufen. Wenn die Beendigungsbedingung in Ordnung ist, kann dies dadurch verursacht werden, dass zu viele rekursive Aufrufe erforderlich sind, bevor sie erfüllt werden.
Mit der GUI-Programmierung ist es jedoch möglich, eine indirekte Rekursion zu generieren . Beispielsweise verarbeitet Ihre App möglicherweise Malmeldungen und ruft während der Verarbeitung eine Funktion auf, die das System veranlasst, eine weitere Malmeldung zu senden. Hier haben Sie sich nicht explizit angerufen, aber das OS / VM hat es für Sie erledigt.
Um mit ihnen fertig zu werden, müssen Sie Ihren Code überprüfen. Wenn Sie Funktionen haben, die sich selbst aufrufen, überprüfen Sie, ob Sie eine Beendigungsbedingung haben. Wenn ja, überprüfen Sie, ob Sie beim Aufrufen der Funktion mindestens eines der Argumente geändert haben. Andernfalls wird die rekursiv aufgerufene Funktion nicht sichtbar geändert, und die Beendigungsbedingung ist unbrauchbar. Beachten Sie auch, dass Ihrem Stack-Speicher möglicherweise der Speicherplatz ausgeht, 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, überprüfen Sie, ob Sie Bibliotheksfunktionen aufrufen, die indirekt dazu führen, dass Ihre Funktion aufgerufen wird (wie im obigen impliziten Fall).
quelle
Um dies zu beschreiben, lassen Sie uns zunächst verstehen, wie lokale Variablen und Objekte gespeichert werden.
Lokale Variablen werden im Stapel gespeichert :
Wenn Sie sich das Bild ansehen, sollten Sie verstehen können, wie die Dinge funktionieren.
Wenn ein Funktionsaufruf von einer Java-Anwendung aufgerufen wird, wird dem Aufrufstapel ein Stapelrahmen zugewiesen. Der Stapelrahmen 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ückgegeben wurde. Wenn kein Platz für einen neuen Stapelrahmen vorhanden ist,
StackOverflowError
wird dieser von der Java Virtual Machine (JVM) ausgelöst.Der häufigste Fall, der möglicherweise den Stapel einer Java-Anwendung erschöpfen kann, ist die Rekursion. Bei der Rekursion ruft sich eine Methode während ihrer Ausführung auf. Rekursion wird als leistungsstarke Allzweckprogrammiertechnik angesehen, muss jedoch mit Vorsicht angewendet werden, um dies zu vermeiden
StackOverflowError
.Ein Beispiel für das Werfen von a
StackOverflowError
ist unten dargestellt:StackOverflowErrorExample.java:
In diesem Beispiel definieren wir eine rekursive Methode,
recursivePrint
die eine Ganzzahl druckt und sich dann selbst aufruft, wobei die nächste aufeinanderfolgende Ganzzahl ein Argument ist. Die Rekursion endet, bis wir0
als Parameter übergeben. In unserem Beispiel haben wir jedoch den Parameter von 1 und seinen zunehmenden Followern übergeben. Folglich wird die Rekursion niemals beendet.Eine Beispielausführung unter Verwendung des
-Xss1M
Flags, das die Größe des Thread-Stapels auf 1 MB angibt, ist unten dargestellt:Abhängig von der ursprünglichen Konfiguration der JVM können die Ergebnisse unterschiedlich sein, aber eventuell
StackOverflowError
werden sie geworfen. 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
Die einfachste Lösung besteht darin, die Stapelspur 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 prüfen und verstehen, warum die Rekursion niemals beendet wird.
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 zu ermöglichen. Abhängig von der installierten Java Virtual Machine (JVM) kann die Standardgröße des Thread-Stacks entweder 512 KB oder 1 MB betragen . Sie können die Thread-Stapelgröße mithilfe des
-Xss
Flags erhöhen . Dieses Flag kann entweder über die Projektkonfiguration oder über die Befehlszeile angegeben werden. Das Format des-Xss
Arguments lautet:-Xss<size>[g|G|m|M|k|K]
quelle
Wenn Sie eine Funktion haben wie:
Dann ruft sich foo () immer weiter auf und wird immer tiefer. Wenn der Speicherplatz, der zum Verfolgen der Funktionen verwendet wird, gefüllt ist, wird der Stapelüberlauffehler angezeigt.
quelle
Stapelüberlauf bedeutet genau das: Ein Stapel läuft über. Normalerweise enthält das Programm einen Stapel, der Variablen und Adressen für den lokalen Bereich enthält, an die zurückgegeben werden soll, wenn die Ausführung einer Routine endet. Dieser Stapel ist in der Regel ein fester Speicherbereich irgendwo im Speicher, daher ist er begrenzt, wie viel er Werte enthalten kann.
Wenn der Stapel leer ist, können Sie nicht platzen. Andernfalls wird ein Stapelunterlauffehler angezeigt.
Wenn der Stapel voll ist, können Sie nicht pushen. Andernfalls wird ein Stapelüberlauffehler angezeigt.
Der Stapelüberlauf wird also dort angezeigt, wo 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 eine Form von Routinen, bei denen der rekursive Aufruf als letzte Aufgabe der Routine angezeigt wird. Ein solcher Routineaufruf wird einfach in einen Sprung reduziert.
Einige Implementierungen gehen so weit, dass sie ihre eigenen Stapel für die Rekursion implementieren. Daher können sie die Rekursion fortsetzen, bis dem System der Speicher ausgeht.
Am einfachsten könnten Sie versuchen, die Stapelgröße zu erhöhen, 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.
quelle
Ein Stapelüberlauf wird normalerweise aufgerufen, indem Funktionsaufrufe zu tief verschachtelt werden (besonders einfach bei Verwendung der Rekursion, dh einer Funktion, die sich selbst aufruft) oder indem eine große Menge an Speicher auf dem Stapel zugewiesen wird, wo die Verwendung des Heaps besser geeignet wäre.
quelle
Wie Sie sagen, müssen Sie Code anzeigen. :-)
Ein Stapelüberlauffehler tritt normalerweise auf, wenn Ihre Funktionsaufrufe zu tief verschachtelt sind. Im Golf- Thread "Stapelüberlaufcode" finden Sie einige Beispiele dafür (obwohl bei dieser Frage die Antworten absichtlich einen Stapelüberlauf verursachen).
quelle
Die häufigste Ursache für Stapelüberläufe ist eine zu tiefe oder unendliche Rekursion . Wenn dies Ihr Problem ist, kann dieses Tutorial zur Java-Rekursion helfen, das Problem zu verstehen.
quelle
StackOverflowError
ist zum Stapel wieOutOfMemoryError
zum Haufen.Ungebundene rekursive Aufrufe führen dazu, dass der Stapelspeicher belegt wird.
Das folgende Beispiel ergibt
StackOverflowError
:StackOverflowError
ist vermeidbar, wenn rekursive Aufrufe begrenzt sind, um zu verhindern, dass die Gesamtsumme unvollständiger speicherinterner Aufrufe (in Bytes) die Stapelgröße (in Bytes) überschreitet.quelle
Hier ist ein Beispiel eines rekursiven Algorithmus zum Umkehren einer einfach verknüpften Liste. Auf einem Laptop mit der folgenden Spezifikation (4 G-Speicher, Intel Core i5 2,3-GHz-CPU, 64-Bit-Windows 7) tritt bei dieser Funktion ein StackOverflow-Fehler für eine verknüpfte Liste mit einer Größe nahe 10.000 auf.
Mein Punkt ist, dass wir die Rekursion mit Bedacht anwenden sollten, wobei wir immer die Größe des Systems berücksichtigen sollten. 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 mit einer Größe von 1 Million in 9 Millisekunden um.)
Iterative Version desselben Algorithmus:
quelle
A
StackOverflowError
ist ein Laufzeitfehler in Java.Es wird ausgelöst, wenn die von JVM zugewiesene Menge an Call-Stack-Speicher überschritten wird.
Ein häufiger Fall eines
StackOverflowError
Wurfs ist, wenn der Aufrufstapel aufgrund einer zu tiefen oder unendlichen Rekursion überschritten wird.Beispiel:
Stapelspur:
Im obigen Fall kann dies durch programmatische Änderungen vermieden werden. Wenn die Programmlogik jedoch korrekt ist und dennoch auftritt, muss die Stapelgröße erhöht werden.
quelle
Dies ist ein typischer Fall von
java.lang.StackOverflowError
... Die Methode ruft sich rekursiv ohne Exit in aufdoubleValue()
.floatValue()
usw.Rational.java
Main.java
Ergebnis
Hier ist der Quellcode von
StackOverflowError
in OpenJDK 7quelle
In einer Krise führt die Situation "Unten" zu einem Stapelüberlauffehler.
}}
quelle
Hier ist ein Beispiel
Ein StackOverflowError ist im Grunde genommen, wenn Sie versuchen, etwas zu tun, das sich höchstwahrscheinlich selbst aufruft und unendlich weitergeht (oder bis es einen StackOverflowError gibt).
add5(a)
ruft sich selbst an und ruft sich dann erneut an und so weiter.quelle
Der Begriff "Stapelüberlauf (Überlauf)" wird häufig verwendet, ist jedoch eine falsche Bezeichnung. Angriffe überlaufen den Stapel nicht, sondern puffern auf dem Stapel.
- aus Vorlesungsfolien von Prof. Dr. Dieter Gollmann
quelle