Speicherleck: Was es ist, typische Szenarien und Diagnose

Autor: IT Sectr Veröffentlicht: 2026-07-29 Lesezeit: 10 Min.

Speicherleck (memory leak) — eine Situation, in der eine Anwendung den von Objekten belegten Speicher nicht freigibt, die nicht mehr benötigt werden. In der mobilen Entwicklung ist dies besonders kritisch: begrenzter Heap und fehlender Swap führen zu OutOfMemoryError und Anwendungsabstürzen. Laut Purdue University (2022) enthalten 35% der Android-Apps im Google Play mindestens ein Speicherleck. Lassen Sie uns typische Szenarien, Diagnosetools und Behebungsmethoden betrachten.

Wichtige Punkte

  • GC Root — Einstiegspunkt, über den der Garbage Collector lebende Objekte bestimmt
  • Context-Leck — Übergabe von Activity Context an ein Singleton hält die gesamte View-Hierarchie fest
  • Handler mit postDelayed — wenn die Activity zerstört wird, verhindert der Handler ihre GC-Freigabe
  • Heap dump — primäre Methode zur Leck-Analyse via MAT oder Android Profiler
  • SoftReference — eine Alternative zu WeakReference für Caches mit automatischer Bereinigung bei Speichermangel

Was ist ein Speicherleck in mobilen Anwendungen?

Speicherleck — eine Situation, in der zugewiesener Speicher nicht an das System zurückgegeben wird, nachdem das Objekt vom Programm nicht mehr benötigt wird. Der Garbage Collector betrachtet ein solches Objekt als lebendig, weil eine aktive Referenzkette von einem GC Root darauf zeigt.

In Java/Kotlin arbeitet der Garbage Collector automatisch, kann aber nicht feststellen, dass ein Objekt logisch nicht mehr benötigt wird, wenn eine technische Referenz darauf existiert. Der Entwickler muss unnötige Verbindungen explizit trennen. In Swift/Objective-C zählt ARC automatisch Referenzen, aber Retain Cycles blockieren das Nullwerden des Zählers.

Die Hauptgefahr von Lecks ist der kumulative Effekt. Jedes Leck verbraucht eine kleine Menge Speicher, aber bei wiederholten Bildschirmwechseln (Bildschirmdrehung, Öffnen/Schließen von Activities) sammeln sich Lecks an, bis das Heap-Limit erschöpft ist.

Wie unterscheidet sich ein Leck von Aufblähung?

Leck — das Objekt ist für den Code unzugänglich, wird aber nicht vom GC entfernt. Aufblähung — das Objekt ist logisch erforderlich, wird aber in übermäßiger Menge gespeichert. Beispiel für Aufblähung: ein Bildercache von 100 MB bei einem Arbeitssatz von 30 MB. Beide Probleme führen zu OOM, aber die Ursachen und Behandlungsmethoden sind unterschiedlich.

Wie funktioniert der Garbage Collector und warum entstehen Lecks?

ART (Android Runtime) verwendet generationsbasierte Garbage Collection mit gleichzeitiger Kompaktierung. Der Speicher wird in junge Generation (Young), alte Generation (Old) und große Objekte (Large) unterteilt. Objekte, die mehrere GC-Zyklen überleben, werden in die Old-Generation verschoben, wo die Sammlung seltener erfolgt — dies beschleunigt normale Zyklen.

Der GC startet, wenn der Heap einen bestimmten Auslastungsschwellenwert (normalerweise 75-85%) erreicht. Während des GC werden alle Anwendungsthreads angehalten (STW — Stop The World). Je mehr lebende Objekte, desto länger die Pause. Lecks erhöhen die Anzahl der lebenden Objekte und verlängern die GC-Pausen.

Der Collector bestimmt lebende Objekte, indem er den Graphen von den GC Roots aus durchläuft: statische Felder, Stapelvariablen aktiver Threads, JNI-Referenzen. Jedes Objekt, das über Referenzen von diesen Wurzeln aus erreichbar ist, gilt als lebendig — selbst wenn der Entwickler weiß, dass es nicht mehr benötigt wird.

kotlin
// Beispiel: statische Sammlung als GC Root — permanentes Leck
object GlobalHolder {
    val listeners = mutableListOf<WeakReference<Any>>()
}

class LeakingFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        GlobalHolder.listeners.add(WeakReference(this))
        // WeakReference verhindert GC nicht — korrektes Verhalten
    }
}

WeakReference löst das Problem: Der GC ignoriert schwache Referenzen bei der Bestimmung lebender Objekte. Wenn nur noch schwache Referenzen auf ein Objekt verweisen, wird es im nächsten GC-Zyklus eingesammelt.

Typische Leck-Szenarien in Android und iOS

Activity Context — das massivste Leck-Szenario in Android. Wenn ein Singleton, ein statisches Feld oder ein langlebiger Dienst eine Referenz auf einen Activity Context speichert, kann die gesamte Activity mit allen Views nicht vom GC eingesammelt werden. Lösung: Verwenden Sie Application Context für langlebige Objekte.

Handler und gesendete Nachrichten — Handler.postDelayed(runnable, delay) stellt eine Nachricht in die Warteschlange des Main Looper. Wenn die Activity vor Ablauf der Verzögerung zerstört wird, ist die Nachricht noch in der Warteschlange und hält eine Referenz über Runnable → anonyme Klasse → äußere Klasse (Activity).

kotlin
class SafeActivity : AppCompatActivity() {
    private val mainHandler = Handler(Looper.getMainLooper())
    private val callback = Runnable { /* update UI */ }

    override fun onResume() {
        super.onResume()
        mainHandler.postDelayed(callback, 5000)
    }

    override fun onPause() {
        mainHandler.removeCallbacks(callback) // erforderlich: Warteschlange leeren
        super.onPause()
    }
}

Innere Klassen — eine nicht-statische innere Klasse hat eine implizite Referenz auf die Instanz der äußeren Klasse. Wenn die äußere Klasse eine Activity ist und die innere Klasse nach außen übergeben wird (z.B. an RecyclerView.Adapter), kann die Activity nicht eingesammelt werden.

  • TimerTask und ScheduledExecutorService — vor der Zerstörung der Activity geplante Aufgaben
  • BroadcastReceiver — nicht in onPause/onDestroy abgemeldet, hält weiterhin den Context
  • ViewModel mit View-Referenz — ViewModel überlebt die Activity, Referenz auf die View führt zu Leck
  • Retrofit Call — wenn Call nicht abgebrochen wird, kommt die Antwort bei zerstörtem Fragment an

Diagnosetools für Speicherlecks

Android Studio Memory Profiler — integriertes Tool für Echtzeit-Heap-Überwachung. Zeigt ein Diagramm des belegten Speichers, die Anzahl der Allokationen und Objekte nach Typ. Ermöglicht die Aufnahme eines Heap Dumps und den Export im HPROF-Format für die Analyse in MAT.

Eclipse MAT (Memory Analyzer Tool) — Desktop-Analyzer für Heap Dumps. Erstellt automatisch Leak Suspects Reports, die Objekte mit der größten retained size hervorheben und für jedes verdächtige Objekt eine vermutete GC-Root-Chain vorschlagen.

Xcode Memory Graph Debugger — für iOS. Hält die Anwendung an und visualisiert den Objektgraphen. Retain Cycles werden rot hervorgehoben; Sie können auf jedes Objekt klicken, um seine Retain Count und Referenzen zu sehen.

WerkzeugFunktionenKomplexität
Memory ProfilerEchtzeit-Diagramm, Heap Dump, Object Allocation TrackingNiedrig
Eclipse MATDominator Tree, Leak Suspects, OQL-AbfragenMittel
LeakCanaryAutomatische Erkennung, Leck-Trace in BenachrichtigungMinimal
Xcode Memory GraphVisueller Retain-Cycle-Graph, Live-ObjektlisteNiedrig

Laut Uber Engineering Blog reduziert die Integration von automatischem Speicher-Profiling (LeakCanary + Heap-Dump-Analyse) in die CI/CD-Pipeline speicherbezogene Vorfälle in der Produktion innerhalb von 3 Monaten um 60%.

Methoden zur Beseitigung von Lecks

Context ersetzen — wenn ein Objekt die Activity überlebt, verwenden Sie applicationContext. Alle langlebigen Objekte (Singletons, Repositories, Datenbank-Helfer) sollten Application Context erhalten, nicht Activity Context. Ausnahme: UI-Komponenten, die Zugriff auf das theme oder Activity-spezifische Ressourcen benötigen.

Lifecycle-aware Komponenten — die Verwendung von LifecycleObserver, DefaultLifecycleObserver oder reaktiven Erweiterungen bricht Abonnements automatisch bei onDestroy ab. Android Jetpack bietet lifecycleScope und viewModelScope, die durch das entsprechende Lebenszyklusereignis bereinigt werden.

Statische innere Klasse — wenn die innere Klasse keinen Zugriff auf die Felder der äußeren Klasse benötigt, machen Sie sie static. Eine statische innere Klasse hat keine implizite Referenz auf die äußere Klasse. Wenn Zugriff benötigt wird, verwenden Sie WeakReference für die explizite Referenz.

kotlin
class MyActivity : AppCompatActivity() {

    // ❌ Nicht-statische innere Klasse — implizite Referenz auf MyActivity
    inner class BadListener : SomeListener {
        override fun onEvent() { /*...*/ }
    }

    // ✅ Statische innere Klasse — keine implizite Referenz
    class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
        override fun onEvent() { /*...*/ }
    }
}

Verwenden Sie in iOS Capture Lists: [weak self] in Closures, die ihren Ersteller überleben können. Für Delegates verwenden Sie schwache Referenzen (weak var delegate). Für Closures, die garantiert nur während der Lebensdauer von self aufgerufen werden, kann [unowned self] verwendet werden, aber mit Vorsicht — der Zugriff auf ein freigegebenes Objekt führt zu einem Absturz.

Häufig gestellte Fragen

Wie finde ich ein Leck ohne spezielle Werkzeuge?

Führen Sie in Android mehrere Bildschirmwechsel durch (Activity A → B → A → B) und überprüfen Sie adb shell dumpsys meminfo package_name. Wenn Total PSS stetig wächst und nicht zum ursprünglichen Wert zurückkehrt — liegt ein Leck vor. In iOS ähnlich: Verwenden Sie Debug Memory Graph in Xcode zur visuellen Überprüfung.

Kann eine Kotlin-Koroutine ein Leck verursachen?

Ja, wenn der CoroutineScope bei der Zerstörung der Komponente nicht abgebrochen wird. Eine in GlobalScope gestartete Koroutine wird auch nach finish() der Activity weiter ausgeführt. Lösung: Verwenden Sie viewModelScope (wird in onCleared abgebrochen) oder lifecycleScope (wird in onDestroy abgebrochen). Für benutzerdefinierte Scopes erstellen Sie lifecycle-aware Scopes über LifecycleOwner.

Wie wirkt sich Bitmap auf Lecks aus?

Bitmap speichert Pixeldaten im nativen Heap, nicht im Java-Heap. Das bedeutet, dass der Java-GC die tatsächliche Größe des Bitmap nicht sieht. Wenn Bitmap nicht mit recycle() aufgerufen oder die Referenz nicht auf null gesetzt wird, wird der native Speicher nicht freigegeben. Verwenden Sie BitmapFactory mit inSampleSize zum Laden verkleinerter Kopien und Glide/Coil für automatische Cache-Verwaltung.

Was ist ein Leck über ein statisches Feld?

Ein statisches Feld ist ein GC Root. Es lebt, solange die Klasse geladen ist (in Android — solange der Process lebt). Wenn ein statisches Feld auf eine Activity, Bitmap, View oder ein anderes schweres Objekt verweist, wird dieses Objekt niemals vom GC eingesammelt. Ein statisches Feld ist eine ewige Referenz. Lösung: Speichern Sie nur WeakReference oder setzen Sie das statische Feld in onDestroy auf null.

Wie vermeide ich Lecks in iOS mit ARC?

ARC gibt Objekte automatisch frei, wenn der starke Referenzzähler auf Null fällt. Ein Retain Cycle ist die einzige Möglichkeit für ein Leck unter ARC. Verwenden Sie immer weak für Eltern→Kind-Referenzen, bei denen das Kind den Elternteil überleben kann (Delegates, Data Sources). Für Closures verwenden Sie die Capture List [weak self] und prüfen Sie self innerhalb der Closure auf nil.

Zusammenfassung

  • Speicherleck — ein Objekt ist für den Code unzugänglich, wird aber nicht vom GC entfernt, weil eine aktive Referenz von einem GC Root existiert
  • GC Roots umfassen statische Felder, Stapelvariablen und JNI-Referenzen; jedes von ihnen erreichbare Objekt ist lebendig
  • Context-Leck — das am weitesten verbreitete Problem in Android: Übergabe von Activity Context an ein Singleton oder statisches Feld
  • Handler und innere Klasse — die zweithäufigste Ursache: nicht abgebrochene Nachrichten in der Looper-Warteschlange halten eine Referenz auf die Activity
  • LeakCanary — das Standardwerkzeug zur automatischen Erkennung; erstellt einen Heap Dump und zeigt die genaue GC-Root-Chain
  • lifecycleScope und viewModelScope lösen das Problem von Lecks durch Koroutinen — automatische Unterbrechung bei Zerstörung
  • Profiling Sie den Speicher in CI/CD: LeakCanary im Debug-Modus + Heap-Dump-Analyse in Testläufen sollten den Merge bei neuen Lecks blockieren

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