Minnesläcka (Memory Leak) — en situation där applikationen håller referenser till objekt som inte längre behövs, vilket förhindrar garbage collectorn från att frigöra occupied memory. Enligt data från LeakCanary förekommer även i väldigt skrivna applikationer 3–5 läckor per 10 000 rader kod. Varje läcka minskar gradvis det tillgängliga minnet, vilket leder till fördröjningar och OutOfMemoryError.
Huvudpunkter
En minnesläcka (Memory Leak) — en situation där ett objekt förblir tillgängligt via en kedja av starka referenser (Strong Reference), trots att det logiskt sett inte längre behövs av applikationen. Garbage collectorn (GC) betraktar ett sådant objekt som levande och frigör inte minnet det upptar. Som ett resultat minskar det tillgängliga heap-minnet ständigt och frekvensen av GC-pauser ökar.
Till skillnad från språk med manuell minneshantering (C, C++), är en läcka i Java/Kotlin inte en glömd free() utan en glömd referens. Så länge det finns en strong reference från rotobjektet (GC Root) till det läckande objektet, anser GC att det behövs. Typiska GC Root: statiska fält, aktiva trådar, anropsstacken, JNI-globala referenser.
Faran med läckor är deras kumulativa effekt. En läcka på 100 KB märks inte, men 100 sådana läckor upptar 10 MB och applikationen börjar sakta ner på grund av frekvent GC. Kritisk massa av läckor leder till OutOfMemoryError och appkraschen. Symtom på läcka: konstant ökning av minnesförbrukning i Profiler-grafen, frekventa GC-pauser med STW (Stop The World) och försämrad UI-prestanda.
Fem typer av läckor täcker 95% av fallen inom mobil utveckling. Varje har sin egen orsak och karakteristiska mönster i koden.
Den mest kända läckan i Android — att lagra en statisk referens till Activity eller Context. Typisk kod: ett statiskt Activity-fält som inte nollställs vid onDestroy(). Så länge det statiska fältet lever, förblir hela Activity med sitt View-träd, som kan uppta 1–10 MB, vid liv. Detta är en klassisk läcka som LeakCanary hittar först.
Lösning: lagra aldrig Activity eller Context i statiska fält. Använd Application Context för singlar som lever längre än Activity. Om du behöver en referens till Activity — använd WeakReference<Activity>.
object MySingleton {
private var weakActivity: WeakReference<Activity>? = null
fun attach(activity: Activity) {
weakActivity = WeakReference(activity)
}
}
Anonyma klasser och icke-statiska nästlade klasser lagrar implicit en referens till den innehållande klassen. En Runnable som skickas till en Handler och körs efter onDestroy() håller hela Activity. Ett Retrofit-anrop som stänger Activity gör samma sak. Detta är den mest lömska typen av läcka — den implicita referensen syns inte i koden.
Kotlin object-uttryck och lambdas fångar också referenser till den yttre klassen. Gör nästlade klasser statiska (eller top-level i Kotlin) och skicka externa referenser via WeakReference. För lambdas, använd Lifecycle-aware-metoden med viewLifecycleOwner.
Prenumeration på systemtjänster utan avregistrering — en direkt läcka. SensorManager, LocationManager, NotificationListener registrerade i onResume() utan att anropa unregister i onPause() håller Activity. Liknande: RxJava Disposable som inte läggs till i CompositeDisposable och en korutin som startats via GlobalScope.
Använd Lifecycle-aware-komponenter: observe() med LifecycleOwner avregistrerar automatiskt vid onDestroy(). För RxJava — viewLifecycleOwner.lifecycle.addObserver med DisposableObserver. För korutiner — lifecycleScope.launch() är bunden till livscykeln.
// automatisk avregistrering via Lifecycle
viewModel.userData.observe(viewLifecycleOwner) { data ->
updateUI(data)
}
// korutiner med lifecycleScope
lifecycleScope.launch {
viewModel.loadData().collect { render(it) }
}
Bitmap upptar en betydande mängd heap-minne: en FullHD-bitmap — 1920 × 1080 × 4 byte = 8.3 MB. Om Bitmap skapas för varje listelement och recycle() inte anropas vid döljning, tar minnet snabbt slut. På gamla Android-versioner (före 3.0) lagrades Bitmap i native-minne, men på moderna — i Dalvik/ART-heapen, och GC kan bara frigöra den om det inte finns någon strong reference.
Använd Glide eller Coil för att ladda bilder — dessa bibliotek hanterar cache och recycle automatiskt. Om du arbetar direkt med Bitmap, anropa bitmap.recycle() för stora bilder som inte längre visas och använd inSampleSize för att ladda förminskade kopior.
Fragment har två livscykler: själva Fragmenten och dess View. Efter onDestroyView() förstörs View-trädet, men själva Fragmenten kan finnas kvar i minnet om det finns en extern referens. Typiskt fel — lagring av referens till Fragment i en ViewPager-adapter eller i navigationsgrafen som inte rensas vid förstöring.
Lagra aldrig referens till Fragment i fält på långlivade objekt. Använd childFragmentManager för nästlade fragment och observe() med LifecycleOwner för dataöverföring mellan dem. ViewPager2 löste detta problem på API-nivå: FragmentTransactionAdapter hanterar livscykeln korrekt.
Upptäckt av en läcka kräver verifiering av två fakta: minnet återvänder inte efter expected lifetime och antalet objekt av en viss typ ökar utan att minska. Diagnosprocessen omfattar tre steg.
Första steget — visuell kontroll via Memory Profiler i Android Studio. Öppna fliken Memory, utför målåtgärden (öppna och stäng skärmen), tryck på GC (Garbage Collection) och se om minnet återgår till den ursprungliga nivån. Om minnet efter 3–4 öppna-stäng-cykler stadigt ökar — finns det en läcka.
Andra steget — ta en Heap Dump. I Memory Profiler tryck på Dump Java Heap. Öppna den erhållna .hprof-filen i Android Studio: du ser alla objekt i heapen med storlekar och referenser. Leta efter klasser vars antal bör vara noll efter att skärmen stängts. Till exempel MainActivity med antalet 2 efter stängning — en tydlig läcka.
Tredje steget — analys av Retained Size och GC Root. I Android Studio analysera Retained Size: hur mycket minne frigörs om du tar bort detta objekt. Vägen från GC Root till objektet visar vad som håller det: Static field → HashMap → Activity — och du ser läckpunkten. Reference widget-panelen visar alla innehavare av objektet.
Fyra verktyg täcker att hitta läckor från automatisk upptäckt till djupgående Heap Dump-analys.
| Verktyg | Metod | Resultatformat |
|---|---|---|
| LeakCanary | Automatisk övervakning | Heap Dump + stack trace för läcka |
| Android Memory Profiler | Manuell övervakning | Minnesgraf + Heap Dump |
| MAT (Eclipse) | Djupgående analys | Dominator Tree-rapport + GC Root-sökväg |
| Perfetto | System-wide spårning | Tidslinje + native-minne |
LeakCanary — must-have för varje Android-projekt. Den upptäcker automatiskt läckor efter att Activity/Fragment-livscykeln avslutats och visar den exakta platsen för läckan med stack trace. Integration: en rad i build.gradle. LeakCanary 2.x kräver ingen manuell initiering — den registrerar automatiskt Application Watcher.
Förebyggande av läckor byggs in i utvecklingsprocessen genom en uppsättning regler och verktyg som kontrollerar koden i varje fas.
Lagra aldrig en referens till Activity, Fragment eller View i ett statiskt fält, singleton eller långlivat objekt. Om en referens är oundviklig — använd WeakReference eller lagra data via ViewModel, som lever exakt så länge som behövs och inte håller View direkt.
ViewModel och LiveData från Android Architecture Components löser livscykelproblemet på arkitekturnivå. ViewModel överlever skärmrotation och innehåller inga referenser till View. LiveData avregistrerar automatiskt observatören vid onDestroy(). Använd dem istället för manuell prenumeration på systemtjänster.
Vid code review var uppmärksam på: statiska fält med typerna Context/View, anonyma klasser, lambdas som stänger Activity, manuella prenumerationer, RxJava disposable utan composite, lagring av Fragment via Bundle. I Kotlin kontrollera dessutom korutiner för launch utan koppling till livscykeln.
LeakCanary kan fungera som en del av testpipen: kör acceptanstester med LeakCanary och markera bygget som misslyckat om en läcka hittas. Detta förhindrar att läckor når produktion. Komplettera kontrollen med Android Lint med regeln StaticFieldLeak — den hittar potentiella läckor på nivån av statisk analys.
// LeakCanary i tester
class LeakTest {
@Test
fun activityShouldNotLeak() {
ActivityScenario.launch(MainActivity::class.java)
.close()
LeakAssertions.assertNoLeak() // fail om det finns en läcka
}
}
Vanliga frågor
Läckan är orsaken och OutOfMemoryError är resultatet. En läcka leder inte till OOM, men ackumulering av flera läckor tömmer Heap. OOM är ett fatalt undantag och en läcka är ett mönster som leder till det över tid.
Via Android Memory Profiler: öppna och stäng skärmen 5 gånger, efter varje stängning anropa GC. Om minnet inte återgår till basnivån — finns det en läcka. Ta en Heap Dump och hitta i listan Activity-klassen vars antal är större än 0 efter stängning.
Delvis. Kotlin löser problemet med null-safety men hanterar inte strong references. Korutiner med lifecycleScope och viewModelScope förhindrar läckor från bakgrundsuppgifter, och sealed class och data class minskar antalet tillstånd som leder till läckor. Det främsta skyddet är arkitekturmönster, inte språkfunktioner.
LeakCanary ger ibland falska positiva: ett objekt kan tillfälligt hållas av systemet (t.ex. InputMethodManager håller den senaste View). Kontrollera manuellt: om Retained Size < 1 KB och GC Root är en systemtjänst, är det troligen ett falsklarm.
Nej. Läckor är möjliga på alla plattformar med GC: iOS (Swift/Objective-C), Flutter (Dart), webbläsare (JavaScript). Mekanismerna är desamma — strong reference från GC Root. På iOS hanterar ARC minnet automatiskt, men retain cycle mellan objekt skapar samma läcka.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också