Memory Leak in mobilen Anwendungen — was es ist, Ursachen und Erkennungsmethoden

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

Ein Memory Leak ist eine Situation, in der eine Anwendung Referenzen auf Objekte behält, die nicht mehr benötigt werden, und so den Garbage Collector daran hindert, den belegten Speicher freizugeben. Laut LeakCanary treten selbst in gut geschriebenen Anwendungen 3–5 Lecks pro 10.000 Codezeilen auf. Jedes Leck reduziert allmählich den verfügbaren Speicher, was zu Verlangsamungen und OutOfMemoryError führt.

Wichtige Punkte

  • Memory Leak — ein Objekt bleibt im Speicher, obwohl es keine aktiven Referenzen aus der Anwendungslogik gibt
  • Statische Referenzen auf Activity oder Context — die häufigste Ursache für Lecks in Android
  • LeakCanary — das Standardwerkzeug zur automatischen Leckerkennung in Android
  • WeakReference und Application Context — grundlegende Techniken zur Leckvermeidung
  • Lifecycle-aware Komponenten beseitigen eine ganze Klasse von Lecks im Zusammenhang mit Abonnements

Was ist ein Memory Leak

Ein Memory Leak ist eine Situation, in der ein Objekt über eine Kette von starken Referenzen (Strong Reference) erreichbar bleibt, obwohl es logisch nicht mehr von der Anwendung benötigt wird. Der Garbage Collector (GC) betrachtet ein solches Objekt als lebendig und gibt den von ihm belegten Speicher nicht frei. Infolgedessen nimmt der verfügbare Heap-Speicher ständig ab und die Häufigkeit der GC-Pausen nimmt zu.

Im Gegensatz zu Sprachen mit manueller Speicherverwaltung (C, C++) ist ein Leck in Java/Kotlin kein vergessenes free(), sondern eine vergessene Referenz. Solange eine starke Referenz von einer GC Root zum ausgelaufenen Objekt existiert, betrachtet der GC es als notwendig. Typische GC Roots: statische Felder, aktive Threads, Aufrufstapel, JNI-globale Referenzen.

Die Gefahr von Lecks ist ihr kumulativer Effekt. Ein einzelnes 100-KB-Leck ist nicht wahrnehmbar, aber 100 solcher Lecks belegen 10 MB, und die Anwendung beginnt aufgrund häufiger GCs zu verlangsamen. Eine kritische Masse von Lecks führt zu OutOfMemoryError und zum Absturz der Anwendung. Symptome eines Lecks: ständiges Wachstum des Speicherverbrauchs im Profiler-Diagramm, häufige GC-Pausen mit STW (Stop The World) und Verschlechterung der UI-Leistung.

Häufige Arten von Lecks in mobilen Anwendungen

Fünf Arten von Lecks decken 95% der Fälle in der mobilen Entwicklung ab. Jede hat ihre eigene Ursache und ein charakteristisches Codemuster.

Statische Referenz auf Activity oder Context

Das bekannteste Leck in Android ist das Speichern einer statischen Referenz auf eine Activity oder einen Context. Typischer Code: ein statisches Activity-Feld, das in onDestroy() nicht auf null gesetzt wird. Solange das statische Feld lebt, lebt die gesamte Activity mit ihrem View-Baum, der 1–10 MB belegen kann. Dies ist das klassische Leck, das LeakCanary zuerst findet.

Lösung: Speichern Sie Activity oder Context niemals in statischen Feldern. Verwenden Sie Application Context für Singletons, die die Activity überleben. Wenn Sie eine Referenz auf eine Activity benötigen, verwenden Sie WeakReference<Activity>.

kotlin
object MySingleton {
    private var weakActivity: WeakReference<Activity>? = null

    fun attach(activity: Activity) {
        weakActivity = WeakReference(activity)
    }
}

Innere Klassen mit impliziter Referenz

Anonyme Klassen und nicht-statische innere Klassen halten implizit eine Referenz auf die umschließende Klasse. Ein Runnable, das an einen Handler übergeben wird und nach onDestroy() ausgeführt wird, hält die gesamte Activity fest. Ein Retrofit-Callback, der eine Activity einfängt, macht dasselbe. Dies ist die tückischste Art von Leck — die implizite Referenz ist im Code nicht sichtbar.

Kotlin-object-Ausdrücke und Lambdas fangen ebenfalls Referenzen auf die äußere Klasse ein. Machen Sie innere Klassen statisch (oder in Kotlin auf oberster Ebene) und übergeben Sie äußere Referenzen über WeakReference. Verwenden Sie für Lambdas einen Lifecycle-aware-Ansatz mit viewLifecycleOwner.

Nicht abgemeldete Listener und Abonnements

Das Abonnieren von Systemdiensten ohne Abmeldung ist ein direktes Leck. SensorManager, LocationManager, NotificationListener, die in onResume() registriert wurden, ohne in onPause() unregister aufzurufen, halten die Activity fest. Ähnlich: RxJava Disposable, das nicht zu CompositeDisposable hinzugefügt wurde, und eine Coroutine, die über GlobalScope gestartet wurde.

Verwenden Sie Lifecycle-aware-Komponenten: observe() mit LifecycleOwner meldet sich automatisch bei onDestroy() ab. Für RxJava — viewLifecycleOwner.lifecycle.addObserver mit DisposableObserver. Für Coroutinen — lifecycleScope.launch() ist an den Lebenszyklus gebunden.

kotlin
// automatische Abmeldung per Lifecycle
viewModel.userData.observe(viewLifecycleOwner) { data ->
    updateUI(data)
}

// Coroutinen mit lifecycleScope
lifecycleScope.launch {
    viewModel.loadData().collect { render(it) }
}

Bitmap ohne recycle

Ein Bitmap belegt eine erhebliche Menge Heap-Speicher: ein FullHD-Bitmap sind 1920 × 1080 × 4 Bytes = 8,3 MB. Wenn für jedes Listenelement ein Bitmap erstellt wird und beim Ausblenden kein recycle() aufgerufen wird, ist der Speicher schnell erschöpft. In älteren Android-Versionen (vor 3.0) wurde Bitmap im nativen Speicher gespeichert, aber in modernen Versionen befindet es sich im Dalvik/ART-Heap, und der GC kann es nur freigeben, wenn keine starke Referenz vorhanden ist.

Verwenden Sie Glide oder Coil zum Laden von Bildern — diese Bibliotheken verwalten Caching und Recycling automatisch. Wenn Sie direkt mit Bitmap arbeiten, rufen Sie bitmap.recycle() für große Bilder auf, die nicht mehr angezeigt werden, und verwenden Sie inSampleSize zum Laden verkleinerter Kopien.

Fragment-Referenz nach onDestroyView

Ein Fragment hat zwei Lebenszyklen: den des Fragments selbst und den seiner View. Nach onDestroyView() wird der View-Baum zerstört, aber das Fragment selbst kann im Speicher bleiben, wenn eine externe Referenz vorhanden ist. Ein typischer Fehler ist das Speichern einer Referenz auf ein Fragment in einem ViewPager-Adapter oder in einem Navigationsgraphen, der bei der Zerstörung nicht gelöscht wird.

Speichern Sie niemals eine Referenz auf ein Fragment in den Feldern langlebiger Objekte. Verwenden Sie childFragmentManager für verschachtelte Fragments und observe() mit LifecycleOwner für die Datenübertragung zwischen ihnen. ViewPager2 hat dieses Problem auf API-Ebene gelöst: FragmentTransactionAdapter verwaltet den Lebenszyklus korrekt.

Wie man einen Memory Leak erkennt

Die Erkennung eines Lecks erfordert die Überprüfung zweier Tatsachen: Der Speicher kehrt nach der erwarteten Lebensdauer nicht zurück, und die Anzahl der Objekte eines bestimmten Typs wächst ohne Abnahme. Der Diagnoseprozess umfasst drei Phasen.

Die erste Phase ist eine visuelle Überprüfung über den Memory Profiler in Android Studio. Öffnen Sie den Memory-Tab, führen Sie die Zielaktion aus (öffnen und schließen Sie den Bildschirm), drücken Sie GC (Garbage Collection) und prüfen Sie, ob der Speicher auf das ursprüngliche Niveau zurückkehrt. Wenn der Speicher nach 3–4 Öffnen-Schließen-Zyklen stetig wächst — liegt ein Leck vor.

Die zweite Phase ist die Erstellung eines Heap Dumps. Drücken Sie im Memory Profiler auf Dump Java Heap. Öffnen Sie die resultierende .hprof-Datei in Android Studio: Sie sehen alle Objekte im Heap mit Größen und Referenzen. Suchen Sie nach Klassen, deren Anzahl nach dem Schließen des Bildschirms Null sein sollte. Beispielsweise ist MainActivity mit einer Anzahl von 2 nach dem Schließen ein offensichtliches Leck.

Die dritte Phase ist die Analyse von Retained Size und GC Root. Analysieren Sie in Android Studio die Retained Size: wie viel Speicher freigegeben wird, wenn Sie dieses Objekt entfernen. Der Pfad von der GC Root zum Objekt zeigt, was es hält: Static field → HashMap → Activity — und Sie sehen den Leckpunkt. Das Reference-Widget-Panel zeigt alle Halter des Objekts.

Werkzeuge zur Leckerkennung

Vier Werkzeuge decken die Leckerkennung von der automatischen Erkennung bis zur tiefgehenden Heap-Dump-Analyse ab.

WerkzeugMethodeErgebnisformat
LeakCanaryAutomatische ÜberwachungHeap Dump + Leck-Stacktrace
Android Memory ProfilerManuelle ÜberwachungSpeicherdiagramm + Heap Dump
MAT (Eclipse)Tiefgehende AnalyseDominator-Tree-Bericht + GC-Root-Pfad
PerfettoSystemweite TracierungZeitachse + nativer Speicher

LeakCanary ist ein Muss für jedes Android-Projekt. Es erkennt automatisch Lecks am Ende des Activity/Fragment-Lebenszyklus und zeigt die genaue Leckstelle mit einem Stacktrace an. Integration: eine Zeile in build.gradle. LeakCanary 2.x erfordert keine manuelle Initialisierung — es registriert automatisch den Application Watcher.

Wie man Memory Leaks verhindert

Die Prävention von Lecks wird durch eine Reihe von Regeln und Werkzeugen in den Entwicklungsprozess integriert, die den Code in jeder Phase prüfen.

Regel für starke Referenzen

Speichern Sie niemals eine Referenz auf eine Activity, ein Fragment oder eine View in einem statischen Feld, Singleton oder langlebigen Objekt. Wenn eine Referenz unvermeidbar ist, verwenden Sie WeakReference oder speichern Sie Daten über ViewModel, das genau so lange lebt wie nötig und keine View direkt hält.

Lifecycle-aware-Architektur

ViewModel und LiveData aus Android Architecture Components lösen das Lebenszyklusproblem auf Architekturebene. ViewModel überlebt die Bildschirmdrehung und enthält keine View-Referenzen. LiveData meldet den Beobachter bei onDestroy() automatisch ab. Verwenden Sie sie anstelle manueller Abonnements von Systemdiensten.

Code-Review mit Fokus auf GC Root

Achten Sie beim Code-Review auf: statische Felder mit Context/View-Typen, anonyme Klassen, Lambdas, die Activity einfangen, manuelle Abonnements, RxJava Disposable ohne Composite, Speichern von Fragment über Bundle. Prüfen Sie in Kotlin zusätzlich Coroutinen auf launch ohne Lebenszyklusbindung.

Automatische Prüfung in CI

LeakCanary kann als Teil der Test-Pipeline arbeiten: Führen Sie Akzeptanztests mit LeakCanary durch und lassen Sie den Build fehlschlagen, wenn ein Leck gefunden wird. Dies verhindert, dass Lecks in die Produktion gelangen. Ergänzen Sie die Prüfung mit der StaticFieldLeak-Regel von Android Lint — sie findet potenzielle Lecks auf der Ebene der statischen Analyse.

kotlin
// LeakCanary in Tests
class LeakTest {
    @Test
    fun activityShouldNotLeak() {
        ActivityScenario.launch(MainActivity::class.java)
            .close()
        LeakAssertions.assertNoLeak() // fehlschlagen, wenn ein Leck vorhanden ist
    }
}

Häufig gestellte Fragen

Was ist der Unterschied zwischen einem Memory Leak und OutOfMemoryError?

Ein Leck ist die Ursache, und OutOfMemoryError ist die Folge. Ein einzelnes Leck führt nicht zu OOM, aber die Ansammlung von Dutzenden von Lecks erschöpft den Heap. OOM ist eine fatale Ausnahme, während ein Leck ein Muster ist, das im Laufe der Zeit dazu führt.

Wie findet man ein Leck ohne LeakCanary?

Durch den Android Memory Profiler: Öffnen und schließen Sie den Bildschirm 5 Mal, rufen Sie nach jedem Schließen GC auf. Wenn der Speicher nicht zum Ausgangsniveau zurückkehrt — liegt ein Leck vor. Erstellen Sie einen Heap Dump und suchen Sie in der Liste nach der Activity-Klasse, deren Anzahl nach dem Schließen größer als 0 ist.

Kann Kotlin Lecks auf Sprachebene verhindern?

Teilweise. Kotlin löst das Null-Safety-Problem, verwaltet aber keine starken Referenzen. Coroutinen mit lifecycleScope und viewModelScope verhindern Lecks durch Hintergrundaufgaben, während sealed class und data class die Anzahl der Zustände reduzieren, die zu Lecks führen. Der Hauptschutz sind Architekturmuster, nicht Sprachfunktionen.

Warum findet LeakCanary ein Leck, das nicht existiert?

LeakCanary liefert manchmal falsch positive Ergebnisse: Ein Objekt kann vorübergehend vom System gehalten werden (z.B. hält InputMethodManager die letzte View fest). Überprüfen Sie manuell: Wenn die Retained Size < 1 KB beträgt und die GC Root ein Systemdienst ist, handelt es sich wahrscheinlich um einen falschen Positivbefund.

Gibt es Memory Leaks nur auf Android?

Nein. Lecks sind auf jeder Plattform mit GC möglich: iOS (Swift/Objective-C), Flutter (Dart), Webbrowser (JavaScript). Die Mechanismen sind dieselben — starke Referenz von einer GC Root. Auf iOS verwaltet ARC den Speicher automatisch, aber retain cycles zwischen Objekten erzeugen dasselbe Leck.

Zusammenfassung

  • Memory Leak — ein Objekt, das der GC aufgrund einer vergessenen starken Referenz nicht freigeben kann
  • Statische Referenzen auf Activity und Context — die häufigste Ursache für Lecks
  • Implizite Referenzen über anonyme Klassen, Lambdas und RxJava-Abonnements sind tückischer als explizite
  • LeakCanary findet Lecks automatisch und zeigt den genauen Stacktrace
  • Lifecycle-aware-Komponenten (ViewModel, LiveData, lifecycleScope) beseitigen eine Klasse von Lecks
  • Heap Dump und Retained-Size-Analyse — die wichtigste Methode der manuellen Diagnose
  • Prävention umfasst Code-Review mit Fokus auf starke Referenzen und CI-Prüfung mit LeakCanary

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