Minnesläcka: vad det är, typiska scenarier och diagnostik

Författare: IT Sectr Publicerad: 2026-07-29 Lästid: 10 min

Minnesläcka (memory leak) — en situation där applikationen inte frigör minne som upptas av objekt som inte längre behövs. Inom mobilutveckling är detta särskilt kritiskt: begränsad heap och avsaknad av swap leder till OutOfMemoryError och att applikationen kraschar. Enligt Purdue University (2022) innehåller 35% av Android-applikationerna i Google Play minst en minnesläcka. Vi går igenom typiska scenarier, diagnostikverktyg och åtgärdsmetoder.

Huvudpunkter

  • GC Root — ingångspunkten genom vilken sophämtaren bestämmer levande objekt
  • Context-läcka — att skicka Activity Context till en singleton leder till att hela View-hierarkin hålls kvar
  • Handler med postDelayed — om Activity förstörs låter Handler det inte gå till GC
  • Heap dump — den primära metoden för att analysera läckor via MAT eller Android Profiler
  • SoftReference — ett alternativ till WeakReference för cacheminnen med automatisk rensning vid minnesbrist

Vad är en minnesläcka i mobilapplikationer?

Minnesläcka — är en situation där allokerat minne inte återlämnas till systemet efter att objektet inte längre behövs av programmet. Sophämtaren betraktar ett sådant objekt som levande eftersom en aktiv referenskedja från GC Root leder till det.

I Java/Kotlin arbetar sophämtaren automatiskt, men den kan inte avgöra att ett objekt logiskt inte behövs om det finns en teknisk referens till det. Utvecklaren måste explicit bryta onödiga förbindelser. I Swift/Objective-C räknar ARC automatiskt referenser, men retain cycles blockerar nollställning av räknaren.

Den största faran med läckor är den kumulativa effekten. Varje läcka förbrukar en liten mängd minne, men vid flera övergångar mellan skärmar (skärmrotationer, öppning/stängning av Activity) ackumuleras läckorna tills heap-gränsen är nådd.

Vad skiljer en läcka från svullnad?

Läcka — objektet är inte tillgängligt för koden men har inte tagits bort av GC. Svullnad — objektet är logiskt nödvändigt men lagras i överdriven mängd. Exempel på svullnad: en bildcache på 100 MB med en arbetsuppsättning på 30 MB. Båda problemen leder till OOM, men orsakerna och behandlingsmetoderna är olika.

Hur fungerar sophämtaren och varför uppstår läckor?

ART (Android Runtime) använder generationsbaserad sophämtning med concurrent compaction. Minnet delas in i ung generation (Young), gammal (Old) och stora objekt (Large). Objekt som överlevt flera GC-cykler flyttas till Old generation, där insamling sker mer sällan — detta snabbar upp vanliga cykler.

GC startar när heap når en viss beläggningströskel (vanligtvis 75-85%). Under GC pausas alla applikationens trådar (STW — Stop The World). Ju fler levande objekt, desto längre paus. Läckor ökar antalet levande objekt och förlänger GC-pauserna.

Sophämtaren bestämmer levande objekt genom att gå igenom grafen från GC Roots: statiska fält, stackvariabler för aktiva trådar, JNI-referenser. Varje objekt som är nåbart via referenser från dessa rötter anses vara levande — även om utvecklaren vet att det inte längre behövs.

kotlin
// Exempel: statisk samling som GC Root — permanent läcka
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 blockerar inte GC — korrekt beteende
    }
}

WeakReference löser problemet: GC ignorerar svaga referenser när det bestämmer levande objekt. Om endast svaga referenser återstår till ett objekt kommer det att samlas in i nästa GC-cykel.

Typiska läckscenarier i Android och iOS

Activity Context — det mest omfattande läckscenariet i Android. Om en singleton, ett statiskt fält eller en långlivad tjänst lagrar en referens till Activity Context, kan hela Activity med alla Views inte samlas in av GC. Lösning: använd Application Context för långlivade objekt.

Handler och skickade meddelanden — Handler.postDelayed(runnable, delay) placerar ett meddelande i kön för Main Looper. Om Activity förstörs innan fördröjningen löper ut, finns meddelandet fortfarande i kön och håller referensen via Runnable → anonym klass → yttre klass (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) // obligatoriskt: rensa kön
        super.onPause()
    }
}

Inner Classes — en icke-statisk inre klass har en implicit referens till instansen av den yttre klassen. Om den yttre klassen är Activity och den inre klassen har skickats någonstans utanför (t.ex. till RecyclerView.Adapter), kan Activity inte samlas in.

  • TimerTask och ScheduledExecutorService — uppgifter som planerats före förstörelsen av Activity
  • BroadcastReceiver — inte avregistrerad i onPause/onDestroy fortsätter att hålla Context
  • ViewModel med referens till View — ViewModel överlever Activity, referens till View leder till läcka
  • Retrofit Call — om Call inte avbryts kommer svaret till det förstörda Fragmentet

Diagnostikverktyg för minnesläckor

Android Studio Memory Profiler — det inbyggda verktyget för realtidsövervakning av heap. Visar en graf över upptaget minne, antal allokeringar och objekt per typ. Möjliggör inspelning av heap dump och export till HPROF-format för analys i MAT.

Eclipse MAT (Memory Analyzer Tool) — skrivbordsanalysator för heap dump. Skapar automatiskt en Leak Suspects Report som markerar objekt med störst retained size och föreslår en möjlig GC-rootkedja för varje misstänkt objekt.

Xcode Memory Graph Debugger — för iOS. Pausar applikationen och visualiserar objektsgrafen. Retain cycles markeras i rött, man kan klicka på valfritt objekt för att se dess retain count och referenser.

VerktygFunktionerKomplexitet
Memory ProfilerRealtidsgraf, heap dump, spårning av objektallokeringLåg
Eclipse MATDominatorträd, läckmisstänkta, OQL-frågorMedel
LeakCanaryAutomatisk upptäckt, läckspår i notisMinimal
Xcode Memory GraphVisuell graf över retain cycles, lista över levande objektLåg

Enligt Uber Engineering Blog minskar införandet av automatisk minnesprofilering (LeakCanary + heap dump-analys) i CI/CD-pipelinen antalet minnesrelaterade incidenter i produktion med 60% inom 3 månader.

Metoder för att åtgärda läckor

Ersätta Context — om objektet lever längre än Activity, använd applicationContext. Alla långlivade objekt (singletoner, repositories, database helpers) bör få Application Context, inte Activity Context. Undantag: UI-komponenter som behöver åtkomst till temaspecifika eller Activity-specifika resurser.

Lifecycle-aware komponenter — användning av LifecycleObserver, DefaultLifecycleObserver eller reactivex avbryter automatiskt prenumerationer vid onDestroy. Android Jetpack tillhandahåller lifecycleScope och viewModelScope, som rensas av motsvarande händelse.

Static inner class — om den inre klassen inte behöver åtkomst till den yttre klassens fält, gör den static. En statisk inre klass har ingen implicit referens till den yttre klassen. Om åtkomst behövs, använd WeakReference för en explicit referens.

kotlin
class MyActivity : AppCompatActivity() {

    // ❌ Icke-statisk inre klass — implicit referens till MyActivity
    inner class BadListener : SomeListener {
        override fun onEvent() { /*...*/ }
    }

    // ✅ Statisk inre klass — ingen implicit referens
    class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
        override fun onEvent() { /*...*/ }
    }
}

I iOS använd capture lists: [weak self] i slutna uttryck som kan överleva sin skapare. För delegater använd svaga referenser (weak var delegate). För slutna uttryck som garanterat endast anropas under selfs livstid kan [unowned self] användas, men försiktigt — åtkomst till ett frigjort objekt orsakar en krasch.

Vanliga frågor

Hur hittar jag en läcka utan specialverktyg?

I Android, gör några övergångar mellan skärmar (Activity A → B → A → B) och kontrollera adb shell dumpsys meminfo package_name. Om Total PSS stadigt ökar och inte återgår till det ursprungliga värdet — finns det en läcka. I iOS på samma sätt: använd Debug Memory Graph i Xcode för visuell kontroll.

Kan en Kotlin-korutin orsaka en läcka?

Ja, om CoroutineScope inte avbryts när komponenten förstörs. En korutin som startats i GlobalScope fortsätter att köras även efter Activitys finish(). Lösning: använd viewModelScope (avbryts i onCleared) eller lifecycleScope (avbryts i onDestroy). För egna Scope, skapa lifecycle-aware scopes via LifecycleOwner.

Hur påverkar Bitmap läckor?

Bitmap lagrar pixeldata i native heap, inte i Java heap. Detta innebär att Java GC inte ser Bitmaps verkliga storlek. Om recycle() inte anropas på Bitmap eller referensen inte nollställs, frigörs inte det ursprungliga minnet. Använd BitmapFactory med inSampleSize för att ladda förminskade kopior och Glide/Coil för automatisk cachehantering.

Vad är en läcka via ett statiskt fält?

Statiskt fält — är en GC Root. Det lever så länge klassen är laddad (i Android — så länge Process lever). Om ett statiskt fält refererar till Activity, Bitmap, View eller något annat tungt objekt, kommer detta objekt aldrig att samlas in av GC. Ett statiskt fält är en evig referens. Lösning: lagra endast WeakReference eller nollställ det statiska fältet i onDestroy.

Hur undviker jag läckor i iOS med ARC?

ARC frigör automatiskt objekt när räknaren för starka referenser sjunker till noll. Retain cycle — det enda sättet att läcka i ARC. Använd alltid weak för parent→child-referenser där childen ska överleva parent (delegater, data source). För slutna uttryck, använd capture list [weak self] och kontrollera self för nil inuti det slutna uttrycket.

Sammanfattning

  • Minnesläcka — objektet är inte tillgängligt för koden men har inte tagits bort av GC eftersom det finns en aktiv referens från GC Root
  • GC Roots inkluderar statiska fält, stackvariabler och JNI-referenser; varje objekt som är nåbart från dem är levande
  • Context-läcka — det mest utbredda problemet i Android: att skicka Activity Context till en singleton eller ett statiskt fält
  • Handler och Inner Class — den näst vanligaste orsaken: icke avbrutna meddelanden i Looper-kön håller en referens till Activity
  • LeakCanary — standardverktyget för automatisk upptäckt; gör heap dump och visar den exakta GC-rootkedjan
  • lifecycleScope och viewModelScope löser problemet med läckor via korutiner — automatisk avbrott vid destroy
  • Profilera minne i CI/CD: LeakCanary i debug + heap dump-analys i testkörning bör blockera merge vid nya läckor

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å