Únik paměti: co to je, typické scénáře a diagnostika

Autor: IT Sectr Publikováno: 2026-07-29 Doba čtení: 10 min

Ú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

  • GC Root — vstupní bod, přes který garbage collector určuje živé objekty
  • Únik Context — předání Activity Context singletonu vede k zadržení celé hierarchie View
  • Handler s postDelayed — pokud je Activity zničeno, Handler mu nedovolí jít na GC
  • Heap dump — hlavní metoda analýzy úniků přes MAT nebo Android Profiler
  • SoftReference — alternativa k WeakReference pro cache s automatickým čištěním při nedostatku paměti

Co je únik paměti v mobilních aplikacích?

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

Čím se únik liší od nadýmání?

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

Jak funguje garbage collector a proč vznikají úniky?

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.

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

Typické scénáře úniků v Android a iOS

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

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

  • TimerTask a ScheduledExecutorService — úkoly naplánované před zničením Activity
  • BroadcastReceiver — nezrušený v onPause/onDestroy nadále drží Context
  • ViewModel s referencí na View — ViewModel přežije Activity, reference na View vede k úniku
  • Retrofit Call — pokud Call není zrušen, odpověď dorazí do zničeného Fragmentu

Diagnostické nástroje pro úniky paměti

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ástrojMožnostiSložitost
Memory ProfilerGraf v reálném čase, heap dump, sledování alokace objektůNízká
Eclipse MATDominator tree, Leak Suspects, OQL dotazyStřední
LeakCanaryAutomatické odhalení, stopa úniku v oznámeníMinimální
Xcode Memory GraphVizuá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ů.

Metody odstranění úniků

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.

kotlin
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

Jak najít únik bez speciálních nástrojů?

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.

Může Kotlin korutina způsobit únik?

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.

Jak Bitmap ovlivňuje úniky?

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.

Co je únik přes statické pole?

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.

Jak se vyhnout únikům v iOS s ARC?

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í

  • Únik paměti — objekt je pro kód nedostupný, ale není odstraněn GC, protože na něj existuje aktivní reference od GC Root
  • GC Roots zahrnují statická pole, zásobníkové proměnné a JNI reference; jakýkoli objekt od nich dosažitelný je živý
  • Únik Context — nejmasivnější problém v Android: předání Activity Context singletonu nebo statickému poli
  • Handler a Inner Class — druhá nejčastější příčina: nezrušené zprávy ve frontě Looper drží referenci na Activity
  • LeakCanary — standardní nástroj pro automatické odhalování; dělá heap dump a ukazuje přesný řetězec GC root
  • lifecycleScope a viewModelScope řeší problém úniků přes korutiny — automatické zrušení při destroy
  • Profitujte paměť v CI/CD: LeakCanary v debug + analýza heap dump v testovacím běhu by měly blokovat merge při nových únicích

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

Prodiskutovat projekt

Přečtěte si také