Stack Overflow in der mobilen Entwicklung — was es ist, Ursachen und Präventionsmethoden

Autor: IT Sectr Veröffentlicht: 2026-03-29 Lesezeit: 9 Min.

Stack Overflow ist ein Aufrufstapelüberlauffehler (java.lang.StackOverflowError), der auftritt, wenn die maximale Stapeltiefe eines Threads überschritten wird. Laut der Java Virtual Machine Specification beträgt die typische Stapeltiefe in der JVM 1024 Rahmen für 64-Bit-Systeme. Die Hauptursache ist endlose Rekursion ohne eine Basisbedingung.

Wichtige Punkte

  • StackOverflowError — ein JVM-Fehler bei Überschreitung der Aufrufstapeltiefenbegrenzung
  • Die Stapeltiefe ist je nach Konfiguration auf 512–2048 Rahmen begrenzt
  • Endlose Rekursion ist die häufigste Ursache für StackOverflowError
  • Endrekursion wird in der JVM im Gegensatz zu funktionalen Sprachen nicht optimiert
  • Iterativer Ersatz der Rekursion ist eine zuverlässige Methode, um Überlauf zu verhindern

Was ist Stack Overflow

StackOverflowError ist ein schwerwiegender Fehler der Java Virtual Machine (JVM) oder Android Runtime (ART), der auftritt, wenn der Aufrufstapel eines Threads die maximal zulässige Tiefe erreicht. Im Gegensatz zu OutOfMemoryError (Heap-Erschöpfung) bezieht sich StackOverflowError auf einen anderen Speicherbereich — den Stapel, in dem Methodenaufrufrahmen und lokale Variablen gespeichert werden.

Jeder Methodenaufruf erzeugt einen Rahmen auf dem Stapel: Rücksprungadresse, Parameter und lokale Variablen. Bei der Rückkehr aus der Methode wird der Rahmen zerstört. Wenn eine Methode sich ohne Basisbedingung selbst aufruft (Rekursion), sammeln sich Rahmen an, bis der Stapel voll ist. Die JVM kann keinen neuen Rahmen zuweisen und wirft StackOverflowError mit einer «null»-Nachricht (in Java) oder mit einem Hinweis auf eine sich endlos wiederholende Stapelzeile.

Die Größe des Stapels eines Threads wird bei der Erstellung festgelegt und ändert sich während der Ausführung nicht. In Android beträgt die typische Stapelgröße des Hauptthreads 32–48 KB, was eine Tiefe von etwa 512–1024 Rahmen für Methoden ohne viele lokale Variablen ergibt. Für Hintergrundthreads ist die Standardgröße kleiner — 16–24 KB.

Wie der Aufrufstapel funktioniert

Der Aufrufstapel (Call Stack) ist eine LIFO-Datenstruktur (Last In, First Out), die die Reihenfolge der Methodenausführung verwaltet. Jedes Mal, wenn das Programm eine Methode aufruft, erstellt die JVM einen Rahmen auf dem Stapel und legt ihn oben ab. Wenn die Methode abgeschlossen ist, wird der Rahmen entfernt.

Jeder Rahmen enthält: Operandenstapel (für Bytecode-Anweisungen), Array lokaler Variablen (einschließlich this), Verweis auf den Konstantenpool und Rücksprungadresse. Je mehr lokale Variablen eine Methode hat, desto größer ist ihr Rahmen und desto weniger Methoden können aufgerufen werden, bevor der Stapel voll ist. Eine Methode mit 10 Parametern und 20 lokalen Variablen benötigt etwa dreimal so viel Platz wie eine Methode ohne Parameter.

Unter Android verwendet ART eine eigene Stapelimplementierung, die sich von der Desktop-JVM unterscheidet. ART kann den Stapel innerhalb bestimmter Grenzen dynamisch vergrößern, aber es gibt dennoch eine feste Grenze für jeden Thread. Der Hauptthread (UI-Thread) hat den größten Stapel, da er den gesamten Activity-Lebenszyklus und die Ereignisverarbeitung übernimmt.

kotlin
// Rekursion, die zu StackOverflowError führt
fun recursiveCall(depth: Int): Int {
    return recursiveCall(depth + 1) // keine Basisbedingung
}

// Der Aufruf verursacht StackOverflowError bei einer Tiefe von ~1000
recursiveCall(0)

Hauptursachen für Stapelüberlauf

Fünf typische Szenarien führen in mobilen Anwendungen zu StackOverflowError. Die meisten hängen mit Rekursion zusammen, aber es gibt auch weniger offensichtliche Ursachen.

Endlose Rekursion ohne Basisbedingung

Die häufigste Ursache. Ein Entwickler schreibt eine rekursive Methode ohne Abbruchbedingung oder mit einer Bedingung, die niemals true wird. Jeder Aufruf fügt einen Rahmen hinzu, und der Stapel füllt sich je nach Rahmengröße in 500–2000 Iterationen. Ein typisches Beispiel: Berechnung der Fakultät n! ohne Überprüfung von n == 0.

Überprüfen Sie die Basisbedingung zu Beginn jeder rekursiven Methode. Verwenden Sie in Kotlin require() oder check() zur Parametervalidierung am Anfang. Erwägen Sie bei tiefer Rekursion (mehr als 100 Ebenen) den Ersatz durch einen iterativen Ansatz.

Zyklische Abhängigkeiten in Konstruktoren

Klasse A erzeugt eine Instanz von B, Klasse B erzeugt eine Instanz von A — dies ist eine zyklische Abhängigkeit in Konstruktoren. Beim Versuch, A zu erzeugen, wird der B-Konstruktor aufgerufen, der den A-Konstruktor aufruft, und so weiter bis zum StackOverflowError. DI-Frameworks (Dagger, Hilt) erkennen solche Zyklen zur Kompilierzeit, aber die manuelle Objekterzeugung fängt sie nicht ab.

Verwenden Sie Abhängigkeitsinjektion mit Abhängigkeitsgraphen: Dagger oder Koin prüfen Zyklen zur Build-Zeit. Wenn ein Zyklus unvermeidbar ist, ersetzen Sie die direkte Abhängigkeit durch eine Schnittstelle mit Lazy-Initialisierung oder einer Provider-Factory.

kotlin
// Zyklische Abhängigkeit — StackOverflowError
class A(private val b: B)
class B(private val a: A)

// Lazy-Lösung
class A(private val bProvider: Provider<B>)

Tiefe Rekursion bei Graphendurchläufen

Das Durchlaufen eines View-Baums (ViewGroup.getChildAt()), Dateisystems oder einer JSON-Struktur durch Rekursion kann die Stapelgrenze bei einer Tiefe von mehr als 500–1000 Elementen überschreiten. Ein Android ViewGroup mit 20 Verschachtelungsebenen ist selten, aber rekursives Parsen von JSON mit 2000 verschachtelten Objekten ist ein reales Szenario.

Ersetzen Sie den rekursiven Durchlauf durch einen iterativen mit einem expliziten Stack<T> oder ArrayDeque. Dies beseitigt das Risiko eines Stapelüberlaufs vollständig, da Heap-Objekte nicht durch die Stapelgrenze beschränkt sind. BFS (Breitensuche) über Queue löst das Problem ebenfalls.

Falsche Behandlung von onConfigurationChanged

Eine Android-spezifische Ursache: zyklische Aufrufe von Lebenszyklusmethoden bei falscher Behandlung der Konfiguration. Zum Beispiel der Aufruf von recreate() innerhalb von onConfigurationChanged, das erneut onConfigurationChanged aufruft, und so weiter bis zum StackOverflowError. Ähnlich: setContentView() innerhalb von onLayout(), das eine weitere Messung und ein weiteres Layout auslöst.

Rufen Sie recreate() nicht innerhalb von Methoden auf, die mit Konfigurationsänderungen zusammenhängen. Verwenden Sie zum Aktualisieren der UI bei Themenwechsel setTheme() ohne recreate. Für dynamische Orientierungsänderungen — rufen Sie requestOrientation() einmal auf, ohne ein Flag in der Konfiguration.

Serialisierung mit zyklischen Referenzen

Gson, Moshi oder Kotlin Serialization geraten beim Versuch, ein Objekt mit zyklischen Referenzen (A verweist auf B, B verweist auf A) zu serialisieren, in endlose Rekursion und stürzen mit StackOverflowError ab. Dies ist ein häufiges Problem beim Serialisieren von Entitäten mit bidirektionalen Beziehungen (JPA, Room mit ForeignKey).

Verwenden Sie @Transient, @JsonIgnore oder @kotlinx.serialization.Transient für eine Seite des Zyklus. Für Gson — JsonSerializer mit expliziter Tiefenbegrenzung. Für Room — serialisieren Sie Entity niemals direkt, verwenden Sie DTO-Mapper.

So diagnostizieren und beheben Sie StackOverflowError

Die Diagnose von StackOverflowError ist einfacher als bei anderen Speicherfehlern: Die Stapelverfolgung zeigt in den meisten Fällen eine sich wiederholende Aufrufsequenz. Dies weist sofort auf Rekursion hin.

Lesen der Stapelverfolgung

Die Stapelverfolgung von StackOverflowError ist einzigartig: Nach den ersten 200–500 Zeilen beginnt sich dasselbe Aufrufmuster zu wiederholen. Die JVM kürzt sich wiederholende Zeilen am Ende und zeigt «... 1234 more». Die Anzahl der sich nicht wiederholenden Zeilen vor «...» gibt die Rekursionstiefe an, die den Fehler verursacht hat.

Lesen Sie die ersten Zeilen der Stapelverfolgung — sie zeigen, mit welcher Methode die Wiederholung begann. Finden Sie die Methode, die sich selbst aufruft oder eine Aufrufkette erzeugt, die zu ihr zurückführt. Korrigieren Sie die Basisbedingung oder ersetzen Sie die Rekursion durch eine Schleife.

Vergrößerung der Stapelgröße (vorübergehende Lösung)

Vorübergehend kann das Problem durch Vergrößerung der Stapelgröße mit dem JVM-Flag -Xss gelöst werden. Für Android wird die Stapelgröße über AndroidManifest festgelegt: android:largeHeap beeinflusst den Stapel nicht. Zum Vergrößern des Thread-Stapels im Code: Thread(ThreadGroup, Runnable, name, stackSize). stackSize ist die gewünschte Größe in Bytes.

kotlin
// Erstellen eines Threads mit vergrößertem Stapel
val thread = Thread(null, runnable, "big-stack-thread", 64 * 1024)
thread.start()

Wichtig: Das Vergrößern des Stapels löst das Problem nicht, sondern verzögert es nur. Bei einer Rekursion von 10.000 Ebenen wird ein 64-KB-Stapel durch einen 128-KB-Stapel ersetzt, was 20.000 Ebenen ergibt — aber der Fehler tritt immer noch auf, nur später. Die einzig richtige Lösung ist der iterative Ersatz der Rekursion.

Rekursion durch Iteration ersetzen

Iterative Algorithmen verwenden nicht den Aufrufstapel zum Speichern von Zwischenzuständen — sie speichern sie im Heap (Stack<T> oder ArrayDeque). Binärbaumdurchlauf, Fakultätsberechnung, Fibonacci — jede Rekursion kann mit einem expliziten Stapel in Iteration umgewandelt werden.

kotlin
// Iterativer Baumdurchlauf — kein Risiko von StackOverflow
fun traverseIterative(root: Node?) {
    val stack = ArrayDeque<Node>()
    stack.push(root)
    while (stack.isNotEmpty()) {
        val node = stack.pop() ?: continue
        process(node)
        node.right?.let { stack.push(it) }
        node.left?.let { stack.push(it) }
    }
}

So verhindern Sie Stack Overflow

Die Prävention von StackOverflowError ist eine Reihe von Regeln und Werkzeugen, die potenzielle rekursive Zyklen erkennen, bevor sie in die Produktion gelangen.

Rekursionstiefenbegrenzung im Debug-Build

Fügen Sie einen schützenden Tiefenzähler in rekursiven Methoden im Debug-Build hinzu. Wenn die Tiefe einen Schwellenwert (z. B. 1000) überschreitet, werfen Sie eine Ausnahme mit einer klaren Nachricht. Dies verwandelt einen StackOverflowError mit unleserlicher Verfolgung in eine verständliche Geschäftsausnahme.

kotlin
fun safeRecursive(n: Int, depth: Int = 0): Int {
    if (depth > 1000) {
        throw IllegalStateException("Rekursion überschritt 1000 Ebenen")
    }
    return if (n <= 1) n
           else safeRecursive(n - 1, depth + 1)
}

Statische Codeanalyse

Detekt (Kotlin) und Infer (Facebook) finden potenziell endlose Rekursionen auf der Ebene der statischen Analyse. Detekt hat eine Regel PotentiallyInfiniteRecursion, die vor Selbstaufrufen ohne Parameteränderung warnt. Aktivieren Sie sie im CI-Regelsatz und setzen Sie den Schweregrad auf error.

Code-Review mit Fokus auf Rekursion

Achten Sie bei der Code-Überprüfung auf: alle Selbstaufruf-Methoden, rekursive Aufrufe innerhalb von Lambdas (Kotlin-Inline-Funktionen), zyklische Aufrufe zwischen verschiedenen Klassen, Rekursion in Property-Delegaten. Überprüfen Sie für jede rekursive Methode: Gibt es eine Basisbedingung, ändert sich der Parameter bei jedem Schritt, garantiert die Parameteränderung das Erreichen der Basisbedingung.

Endrekursive Transformation (eingeschränkt)

Kotlin unterstützt den tailrec-Modifikator: Wenn eine rekursive Methode mit tailrec markiert ist und der Aufruf endständig (letzte Operation) ist, wandelt der Compiler sie in Iteration um. Allerdings funktioniert tailrec nur für Selbstaufrufe (Methode ruft sich direkt selbst auf), nicht für gegenseitige Rekursion und wird in Android-kompatiblen Kotlin-Versionen vor 1.5 nicht unterstützt.

kotlin
tailrec fun factorial(n: Int, acc: Int = 1): Int {
    return if (n <= 1) acc
           else factorial(n - 1, acc * n) // Endaufruf
}

Häufig gestellte Fragen

Kann StackOverflowError mit try-catch abgefangen werden?

Ja, aber nur auf Java-Ebene. Error ist wie Exception ein Throwable. Allerdings ist der Stapel nach einem StackOverflowError beschädigt — Rahmen, die nicht passten, können nicht korrekt abgeschlossen werden. Der Versuch, im catch-Block ein neues Objekt zu erstellen, kann einen weiteren StackOverflowError auslösen.

Wie groß ist der Standard-Stapel in Android?

Für den Hauptthread — 32–48 KB, für Hintergrundthreads — 16–24 KB. Die genaue Größe hängt von der Android-Version und dem Gerätehersteller ab. ART verwendet dynamische Stapelerweiterung, jedoch nicht mehr als das 2-fache des Anfangswerts.

Kann Endrekursion StackOverflowError verhindern?

In Kotlin — ja, wenn die Methode mit tailrec markiert ist. Der Compiler wandelt Endrekursion in Iteration um und beseitigt so das Stapelwachstum vollständig. In Java wird Endrekursion von der JVM nicht optimiert (im Gegensatz zu funktionalen Sprachen wie Scala).

Warum tritt StackOverflowError im Emulator auf, aber nicht auf dem Gerät?

Die Stapelgröße kann im Emulator und auf einem echten Gerät unterschiedlich sein. Der Emulator verwendet eine Desktop-JVM mit einem typischen Stapel von 512–1024 KB, während Android ART 32–48 KB verwendet. Der Fehler tritt auf ART früher auf als auf der Desktop-JVM.

Wie unterscheidet sich StackOverflowError von OutOfMemoryError?

Speicherbereich: StackOverflowError ist ein Stapelfehler (Aufrufrahmen), OutOfMemoryError ein Heap-Fehler (Objekte). StackOverflowError wird fast immer durch Rekursion verursacht, während OutOfMemoryError durch Speicherlecks oder große Objekte verursacht wird.

Zusammenfassung

  • StackOverflowError — Aufrufstapelüberlauf bei Überschreitung der Rekursionstiefenbegrenzung
  • Die Stapeltiefe in Android beträgt 512–1024 Rahmen auf dem Hauptthread
  • Endlose Rekursion ist die Hauptursache; überprüfen Sie die Basisbedingung in jeder rekursiven Methode
  • Zyklische Abhängigkeiten in Konstruktoren — eine weniger offensichtliche, aber häufige Überlaufursache
  • Iterativer Ersatz der Rekursion durch expliziten Stack<T> beseitigt das Risiko vollständig
  • tailrec in Kotlin wandelt Endrekursion auf Compiler-Ebene in Iteration um
  • Statische Analyse (Detekt, Infer) findet potenziell endlose Rekursionen vor der Ausführung

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch