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
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.
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.
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.
// 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.
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).
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.
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.
| Verktyg | Funktioner | Komplexitet |
|---|---|---|
| Memory Profiler | Realtidsgraf, heap dump, spårning av objektallokering | Låg |
| Eclipse MAT | Dominatorträd, läckmisstänkta, OQL-frågor | Medel |
| LeakCanary | Automatisk upptäckt, läckspår i notis | Minimal |
| Xcode Memory Graph | Visuell graf över retain cycles, lista över levande objekt | Lå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.
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.
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
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.
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.
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.
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.
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
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å