Únik paměti (memory leak) — situace, kdy aplikace neuvolňuje paměť obsazenou objekty, které již nejsou potřeba. V mobilním vývoji je to obzvláště kritické: omezený heap a absence swapu vedou k OutOfMemoryError a pádu aplikace. Podle Purdue University (2022) obsahuje 35% Android aplikací v Google Play alespoň jeden únik paměti. Rozebereme typické scénáře, diagnostické nástroje a metody odstranění.
Hlavní body
Únik paměti — je situace, kdy přidělená paměť není vrácena systému poté, co objekt přestal být pro program potřebný. Garbage collector považuje takový objekt za živý, protože k němu vede aktivní řetězec referencí od GC Root.
V Java/Kotlin garbage collector pracuje automaticky, ale nemůže určit, že objekt logicky není potřeba, pokud na něj existuje technická reference. Vývojář musí explicitně přerušit nepotřebná spojení. V Swift/Objective-C ARC automaticky počítá reference, ale retain cycles blokují vynulování čítače.
Hlavní nebezpečí úniků — kumulativní efekt. Každý únik spotřebovává malé množství paměti, ale při opakovaných přechodech mezi obrazovkami (otáčení obrazovky, otevírání/zavírání Activity) se úniky hromadí, dokud není vyčerpán limit heapu.
Únik — objekt je pro kód nedostupný, ale nebyl odstraněn GC. Nadýmání — objekt je logicky potřebný, ale je ukládán v nadměrném množství. Příklad nadýmání: cache obrázků o velikosti 100 MB s pracovní sadou 30 MB. Oba problémy vedou k OOM, ale příčiny a metody léčby jsou odlišné.
ART (Android Runtime) používá generační garbage collection s concurrent compaction. Paměť se dělí na mladou generaci (Young), starou (Old) a obrovské objekty (Large). Objekty, které přežily několik GC cyklů, jsou přesunuty do Old generation, kde ke sběru dochází méně často — to zrychluje běžné cykly.
GC začíná, když heap dosáhne určitého prahu obsazenosti (obvykle 75-85%). Během GC jsou všechna vlákna aplikace pozastavena (STW — Stop The World). Čím více živých objektů, tím delší pauza. Úniky zvyšují počet živých objektů a prodlužují GC pauzy.
Collector určuje živé objekty procházením grafu od GC Roots: statická pole, zásobníkové proměnné aktivních vláken, JNI reference. Jakýkoli objekt dosažitelný pomocí referencí od těchto kořenů je považován za živý — i když vývojář ví, že už není potřeba.
// Příklad: statická kolekce jako GC Root — trvalý únik
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 neblokuje GC — správné chování
}
}
WeakReference řeší problém: GC ignoruje slabé reference při určování živých objektů. Pokud na objekt zbývají pouze slabé reference, bude shromážděn v příštím GC cyklu.
Activity Context — nejmasivnější scénář úniků v Androidu. Pokud singleton, statické pole nebo dlouho žijící služba uchovává referenci na Activity Context, celé Activity se všemi View nemůže být GC shromážděno. Řešení: používejte Application Context pro dlouho žijící objekty.
Handler a odeslané zprávy — Handler.postDelayed(runnable, delay) umístí zprávu do fronty Main Looper. Pokud je Activity zničeno před vypršením zpoždění, zpráva je stále ve frontě a drží referenci přes Runnable → anonymní třídu → vnější třídu (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) // povinné: vyčistit frontu
super.onPause()
}
}
Inner Classes — nestatická vnitřní třída má implicitní referenci na instanci vnější třídy. Pokud je vnější třídou Activity a vnitřní třída je předána někam ven (např. do RecyclerView.Adapter), Activity nemůže být shromážděno.
Android Studio Memory Profiler — vestavěný nástroj pro monitorování heapu v reálném čase. Zobrazuje graf obsazené paměti, počet alokací a objektů podle typu. Umožňuje zaznamenat heap dump a exportovat do formátu HPROF pro analýzu v MAT.
Eclipse MAT (Memory Analyzer Tool) — desktopový analyzátor heap dump. Automaticky vytváří Leak Suspects Report, který zvýrazňuje objekty s největším retained size a navrhuje pravděpodobný řetězec GC root pro každý podezřelý objekt.
Xcode Memory Graph Debugger — pro iOS. Pozastaví aplikaci a vizualizuje graf objektů. Retain cycles jsou zvýrazněny červeně, lze kliknout na libovolný objekt a zobrazit jeho retain count a reference.
| Nástroj | Možnosti | Složitost |
|---|---|---|
| Memory Profiler | Graf v reálném čase, heap dump, sledování alokace objektů | Nízká |
| Eclipse MAT | Dominator tree, Leak Suspects, OQL dotazy | Střední |
| LeakCanary | Automatické odhalení, stopa úniku v oznámení | Minimální |
| Xcode Memory Graph | Vizuální graf retain cycles, seznam živých objektů | Nízká |
Podle Uber Engineering Blog snižuje zavedení automatického profilování paměti (LeakCanary + analýza heap dump) v CI/CD pipeline počet incidentů souvisejících s pamětí v produkci o 60% během 3 měsíců.
Nahrazení Context — pokud objekt žije déle než Activity, použijte applicationContext. Všechny dlouho žijící objekty (singletony, repozitáře, database helpers) by měly dostávat Application Context, nikoli Activity Context. Výjimka: UI komponenty, které potřebují přístup k tématu nebo zdrojům specifickým pro Activity.
Lifecycle-aware komponenty — použití LifecycleObserver, DefaultLifecycleObserver nebo reactivex automaticky ruší odběry při onDestroy. Android Jetpack poskytuje lifecycleScope a viewModelScope, které jsou čištěny odpovídající událostí.
Static inner class — pokud vnitřní třída nepotřebuje přístup k polím vnější třídy, udělejte ji static. Statická vnitřní třída nemá implicitní referenci na vnější třídu. Pokud je přístup potřebný, použijte WeakReference pro explicitní referenci.
class MyActivity : AppCompatActivity() {
// ❌ Nestatická vnitřní třída — implicitní reference na MyActivity
inner class BadListener : SomeListener {
override fun onEvent() { /*...*/ }
}
// ✅ Statická vnitřní třída — žádná implicitní reference
class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
override fun onEvent() { /*...*/ }
}
}
V iOS používejte capture lists: [weak self] v uzávěrech, které mohou přežít svého tvůrce. Pro delegáty používejte slabé reference (weak var delegate). Pro uzávěry, které jsou zaručeně volány pouze za života self, lze použít [unowned self], ale opatrně — přístup k uvolněnému objektu způsobí crash.
Často kladené otázky
V Android proveďte několik přechodů mezi obrazovkami (Activity A → B → A → B) a zkontrolujte adb shell dumpsys meminfo package_name. Pokud Total PSS stabilně roste a nevrací se na původní hodnotu — jedná se o únik. V iOS podobně: použijte Debug Memory Graph v Xcode pro vizuální kontrolu.
Ano, pokud CoroutineScope není zrušen při zničení komponenty. Korutina spuštěná v GlobalScope pokračuje v provádění i po finish() Activity. Řešení: používejte viewModelScope (ruší se v onCleared) nebo lifecycleScope (ruší se v onDestroy). Pro vlastní Scope vytvářejte lifecycle-aware scopes přes LifecycleOwner.
Bitmap ukládá pixelová data v native heap, nikoli v Java heap. To znamená, že Java GC nevidí skutečnou velikost Bitmap. Pokud na Bitmap není zavoláno recycle() nebo reference není vynulována, nativní paměť se neuvolní. Používejte BitmapFactory s inSampleSize pro načítání zmenšených kopií a Glide/Coil pro automatickou správu cache.
Statické pole — je GC Root. Žije tak dlouho, dokud je třída načtena (v Android — dokud žije Process). Pokud statické pole referencuje Activity, Bitmap, View nebo jakýkoli jiný těžký objekt, tento objekt nebude nikdy shromážděn GC. Statické pole — věčná reference. Řešení: ukládejte pouze WeakReference nebo vynulujte statické pole v onDestroy.
ARC automaticky uvolňuje objekty, když čítač silných referencí klesne na nulu. Retain cycle — jediný způsob úniku při ARC. Vždy používejte weak pro parent→child reference, kde child má přežít parent (delegáti, data source). Pro uzávěry používejte capture list [weak self] a kontrolujte self na nil uvnitř uzávěru.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také