Minnesläcka i mobilapplikationer — vad det är, orsaker och metoder för att hitta

Författare: IT Sectr Publicerad: 2026-03-29 Lästid: 9 min

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

  • Memory Leak — ett objekt finns kvar i minnet trots att det inte finns aktiva referenser från applikationslogiken
  • Statiska referenser till Activity eller Context — den vanligaste orsaken till läckor i Android
  • LeakCanary — standardverktyget för automatisk upptäckt av läckor i Android
  • WeakReference och Application Context — grundläggande tekniker för att förhindra läckor
  • Lifecycle-aware-komponenter eliminerar en hel klass av läckor relaterade till prenumerationer

Vad är en minnesläcka

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.

Typiska typer av läckor i mobilappar

Fem typer av läckor täcker 95% av fallen inom mobil utveckling. Varje har sin egen orsak och karakteristiska mönster i koden.

Statisk referens till Activity eller Context

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>.

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

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

Inre klasser med implicit referens

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.

Ej avregistrerade lyssnare och prenumerationer

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.

kotlin
// automatisk avregistrering via Lifecycle
viewModel.userData.observe(viewLifecycleOwner) { data ->
    updateUI(data)
}

// korutiner med lifecycleScope
lifecycleScope.launch {
    viewModel.loadData().collect { render(it) }
}

Bitmap utan recycle

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-referens efter onDestroyView

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.

Hur man upptäcker en minnesläcka

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.

Verktyg för att hitta läckor

Fyra verktyg täcker att hitta läckor från automatisk upptäckt till djupgående Heap Dump-analys.

VerktygMetodResultatformat
LeakCanaryAutomatisk övervakningHeap Dump + stack trace för läcka
Android Memory ProfilerManuell övervakningMinnesgraf + Heap Dump
MAT (Eclipse)Djupgående analysDominator Tree-rapport + GC Root-sökväg
PerfettoSystem-wide spårningTidslinje + 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.

Hur man förhindrar minnesläckor

Förebyggande av läckor byggs in i utvecklingsprocessen genom en uppsättning regler och verktyg som kontrollerar koden i varje fas.

Regeln om starka referenser

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.

Lifecycle-aware-arkitektur

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.

Code Review med fokus på GC Root

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.

Automatisk kontroll i CI

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.

kotlin
// 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

Vad är skillnaden mellan en minnesläcka och OutOfMemoryError?

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.

Hur hittar man en läcka utan LeakCanary?

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.

Kan Kotlin förhindra läckor på språknivå?

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.

Varför hittar LeakCanary en läcka som inte finns?

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.

Förekommer minnesläckor bara på Android?

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

  • Memory Leak — ett objekt som GC inte kan frigöra på grund av en glömd strong reference
  • Statiska referenser till Activity och Context — den vanligaste orsaken till läckor
  • Implicita referenser via anonyma klasser, lambdas och RxJava-prenumerationer är lömskare än explicita
  • LeakCanary hittar automatiskt läckor och visar exakt stack trace
  • Lifecycle-aware-komponenter (ViewModel, LiveData, lifecycleScope) eliminerar en klass av läckor
  • Heap Dump och Retained Size-analys — den främsta metoden för manuell diagnos
  • Förebyggande inkluderar code review på strong reference och CI-kontroll med LeakCanary

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.

Diskutera projektet

Läs också