Was kann einen java.lang.StackOverflowError
verursachen? Der Stack-Ausdruck, den ich erhalte, ist überhaupt nicht sehr tief (nur 5 Methoden).
Antworten
Zu viele Anzeigen?Überprüfen Sie mögliche rekursive Aufrufe von Methoden. Meistens entsteht dies, wenn eine Methode sich selbst aufruft. Ein einfaches Beispiel ist
public static void main(String... args) {
Main main = new Main();
main.testMethod(1);
}
public void testMethod(int i) {
testMethod(i);
System.out.println(i);
}
Hier wird System.out.println(i); wiederholt auf den Stack gelegt, wenn testMethod aufgerufen wird.
Eines der (optionalen) Argumente für die JVM ist die Stapelgröße. Es ist -Xss. Ich weiß nicht, was der Standardwert ist, aber wenn die Gesamtmenge an Zeug im Stapel diesen Wert überschreitet, erhalten Sie diesen Fehler.
Im Allgemeinen ist die unendliche Rekursion die Ursache dafür, aber wenn Sie das sehen würden, würde Ihr Stack-Trace mehr als 5 Frames haben.
Versuchen Sie, ein -Xss-Argument hinzuzufügen (oder den Wert eines zu erhöhen), um zu sehen, ob dies verschwindet.
Was ist java.lang.StackOverflowError
Der Fehler java.lang.StackOverflowError
wird geworfen, um anzuzeigen, dass der Stapel der Anwendung aufgrund einer tiefen Rekursion erschöpft wurde, d.h. Ihr Programm/Skript rekursiert zu tief.
Details
Der StackOverflowError
erweitert die Klasse VirtualMachineError
, die anzeigt, dass die JVM keine Ressourcen mehr hat oder dass sie nicht mehr funktionieren kann. Das VirtualMachineError
, das die Klasse Error
erweitert, wird verwendet, um auf schwerwiegende Probleme hinzuweisen, die eine Anwendung nicht abfangen sollte. Eine Methode darf solche Fehler nicht in ihrem throw
-Klausel deklarieren, weil es sich um abnormale Bedingungen handelt, die nie erwartet wurden.
Ein Beispiel
Minimal, vollständiges und überprüfbares Beispiel
:
Paketdemo;
public class StackOverflowErrorBeispiel {
public static void main(String[] args)
{
StackOverflowErrorBeispiel.recursivePrint(1);
}
public static void recursivePrint(int num) {
System.out.println("Nummer: " + num);
if(num == 0)
return;
else
recursivePrint(++num);
}
}
Konsolenausgabe
Nummer: 1
Nummer: 2
.
.
.
Nummer: 8645
Nummer: 8646
Nummer: 8647Exception in thread "main" java.lang.StackOverflowError
at java.io.FileOutputStream.write(Unknown Source)
at java.io.BufferedOutputStream.flushBuffer(Unknown Source)
at java.io.BufferedOutputStream.flush(Unknown Source)
at java.io.PrintStream.write(Unknown Source)
at sun.nio.cs.StreamEncoder.writeBytes(Unknown Source)
at sun.nio.cs.StreamEncoder.implFlushBuffer(Unknown Source)
at sun.nio.cs.StreamEncoder.flushBuffer(Unknown Source)
at java.io.OutputStreamWriter.flushBuffer(Unknown Source)
at java.io.PrintStream.newLine(Unknown Source)
at java.io.PrintStream.println(Unknown Source)
at demo.StackOverflowErrorBeispiel.recursivePrint(StackOverflowErrorBeispiel.java:11)
at demo.StackOverflowErrorBeispiel.recursivePrint(StackOverflowErrorBeispiel.java:16)
.
.
.
at demo.StackOverflowErrorBeispiel.recursivePrint(StackOverflowErrorBeispiel.java:16)
Erklärung
Wenn ein Funktionsaufruf von einer Java-Anwendung aufgerufen wird, wird ein Stapelrahmen im Aufrufstapel zugewiesen. Der Stapelrahmen
enthält die Parameter der aufgerufenen Methode, ihre lokalen Parameter und die Rückgabeadresse der Methode. Die Rückgabeadresse gibt den Ausführungspunkt an, von dem aus die Programmablauf fortgesetzt wird, nachdem die aufgerufene Methode zurückgegeben hat. Wenn kein Platz für einen neuen Stapelrahmen vorhanden ist, wird der StackOverflowError
vom Java Virtual Machine (JVM) geworfen.
Der häufigste Fall, der eine Java-Anwendung erschöpfen kann, ist die Rekursion. Bei der Rekursion ruft eine Methode sich während ihrer Ausführung selbst auf. Rekursion
ist eine der leistungsstärksten und vielseitigsten Programmierungstechniken, die jedoch mit Vorsicht verwendet werden muss, um den StackOverflowError
zu vermeiden.
Referenzen
Was tatsächlich zu einem java.lang.StackOverflowError führt, ist in der Regel unbeabsichtigte Rekursion. Bei mir passiert das oft, wenn ich beabsichtige, eine Supermethode für die überschriebene Methode aufzurufen. Wie in diesem Fall:
public class Vehicle {
public void accelerate(float acceleration, float maxVelocity) {
// setze die Beschleunigung
}
}
public class SpaceShip extends Vehicle {
@Override
public void accelerate(float acceleration, float maxVelocity) {
// aktualisiere den Flusskondensator und rufe super.accelerate auf
// Ups, ich wollte eigentlich super.accelerate(acceleration, maxVelocity) aufrufen;
// habe stattdessen das hier geschrieben. Ein StackOverflow liegt in unserer Zukunft.
this.accelerate(acceleration, maxVelocity);
}
}
Zunächst ist es nützlich zu wissen, was passiert, wenn wir eine Funktion aufrufen. Die Argumente und die Adresse, von wo aus die Methode aufgerufen wurde, werden auf den Stapel geschoben (siehe http://de.wikipedia.org/wiki/Stapel_(Datenstruktur)#Speicherverwaltung_zu_Laufzeit), damit die aufgerufene Methode auf die Argumente zugreifen kann und damit die Ausführung nach dem Aufruf fortgesetzt werden kann. Da wir jedoch this.accelerate(acceleration, maxVelocity) rekursiv aufrufen (Rekursion ist grob gesagt, wenn eine Methode sich selbst aufruft. Für mehr Informationen siehe http://de.wikipedia.org/wiki/Rekursion_(Informatik)), befinden wir uns in einer Situation, die als unendliche Rekursion bekannt ist, und wir stapeln die Argumente und die Rückgabeadresse auf dem Aufrufstapel. Da der Aufrufstapel eine begrenzte Größe hat, gehen uns letztendlich die Ressourcen aus. Das Aufbrauchen des Speicherplatzes auf dem Aufrufstapel wird als Überlauf bezeichnet. Das liegt daran, dass wir versuchen, mehr Stapelplatz zu verwenden, als wir haben, und die Daten buchstäblich überlaufen den Stapel. In der Java-Programmiersprache führt dies zu der Laufzeit-Ausnahme java.lang.StackOverflow und wird das Programm sofort stoppen.
Das obige Beispiel ist etwas vereinfacht (obwohl es mir öfter passiert, als ich zugeben möchte). Das Gleiche kann auch auf einem etwas verschlungeneren Weg passieren, was es etwas schwieriger macht, es zu verfolgen. Im Allgemeinen ist der StackOverflow jedoch normalerweise ziemlich einfach zu lösen, sobald er auftritt.
Theoretisch ist es auch möglich, einen Stapelüberlauf ohne Rekursion zu haben, aber in der Praxis scheint dies ein ziemlich seltenes Ereignis zu sein.
Lösung für Hibernate-Benutzer beim Parsen von Daten:
Ich hatte diesen Fehler, weil ich eine Liste von Objekten geparst habe, die auf beiden Seiten mit @OneToMany
und @ManyToOne
verknüpft waren, um sie mit Jackson in json umzuwandeln, was eine Endlosschleife verursachte.
Wenn Sie sich in derselben Situation befinden, können Sie dies lösen, indem Sie die Annotationen @JsonManagedReference
und @JsonBackReference
verwenden.
Definitionen aus der API:
-
JsonManagedReference (https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonManagedReference.html):
Annotation, die angibt, dass die annotierte Eigenschaft Teil einer zweiseitigen Verknüpfung zwischen Feldern ist; und dass ihre Rolle die eines "parent" (oder "forward") Links ist. Der Werttyp (Klasse) der Eigenschaft muss über eine einzelne kompatible Eigenschaft verfügen, die mit JsonBackReference annotiert ist. Die Verknüpfung wird so gehandhabt, dass die mit dieser Annotation annotierte Eigenschaft normal gehandhabt wird (normal serialisiert, keine spezielle Behandlung bei der Deserialisierung); es ist der übereinstimmende Rückverweis, der eine spezielle Behandlung erfordert.
-
JsonBackReference: (https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonBackReference.html):
Annotation, die angibt, dass die zugehörige Eigenschaft Teil einer zweiseitigen Verknüpfung zwischen Feldern ist; und dass ihre Rolle die eines "child" (oder "back") Links ist. Der Werttyp der Eigenschaft muss ein Bean sein: es kann nicht eine Collection, Map, Array oder Enumeration sein. Die Verknüpfung wird so gehandhabt, dass die mit dieser Annotation annotierte Eigenschaft nicht serialisiert wird; und während der Deserialisierung wird ihr Wert auf die Instanz gesetzt, die den "managed" (forward) Link hat.
Beispiel:
Owner.java:
@JsonManagedReference
@OneToMany(mappedBy = "owner", fetch = FetchType.EAGER)
Set cars;
Car.java:
@JsonBackReference
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "owner_id")
private Owner owner;
Eine andere Lösung besteht darin, @JsonIgnore
zu verwenden, was das Feld einfach auf null setzt.
- See previous answers
- Weitere Antworten anzeigen