Únik paměti v mobilních aplikacích — co to je, příčiny a metody vyhledávání

Autor: IT Sectr Publikováno: 2026-03-29 Doba čtení: 9 min

Únik paměti (Memory Leak) — situace, kdy aplikace drží reference na objekty, které již nejsou potřeba, a brání garbage collectoru uvolnit occupied memory. Podle údajů LeakCanary se i v dobře napsaných aplikacích vyskytují 3–5 úniků na 10 000 řádků kódu. Každý únik postupně snižuje dostupnou paměť, což vede ke zpomalení a OutOfMemoryError.

Nejdůležitější

  • Memory Leak — objekt zůstává v paměti, i když na něj nejsou aktivní reference z logiky aplikace
  • Statické reference na Activity nebo Context — nejčastější příčina úniků v Androidu
  • LeakCanary — standardní nástroj pro automatickou detekci úniků v Androidu
  • WeakReference a Application Context — základní techniky prevence úniků
  • Lifecycle-aware komponenty eliminují celou třídu úniků spojených s předplatným

Co je únik paměti

Únik paměti (Memory Leak) — situace, kdy objekt zůstává dosažitelný prostřednictvím řetězce silných referencí (Strong Reference), přestože logicky již není pro aplikaci potřeba. Garbage collector (GC) považuje takový objekt za živý a neuvolňuje jím zabranou paměť. V důsledku toho se dostupná paměť haldy (Heap) neustále snižuje a četnost GC pauz roste.

Na rozdíl od jazyků s ručním správou paměti (C, C++), v Java/Kotlin není únik zapomenutý free(), ale zapomenutá reference. Dokud existuje strong reference od kořenového objektu (GC Root) k unikajícímu objektu, GC jej považuje za potřebný. Typické GC Root: statická pole, aktivní vlákna, zásobník volání, JNI-globální reference.

Nebezpečí úniků je jejich kumulativní efekt. Jeden únik 100 KB není patrný, ale 100 takových úniků zabírá 10 MB a aplikace začíná zpomalovat kvůli častým GC. Kritická masa úniků vede k OutOfMemoryError a pádu aplikace. Příznaky úniku: stálý růst spotřeby paměti v grafu Profileru, časté GC pauzy s STW (Stop The World) a pokles výkonu UI.

Typické druhy úniků v mobilních aplikacích

Pět druhů úniků pokrývá 95% případů v mobilním vývoji. Každý má svou příčinu a charakteristický vzor v kódu.

Statická reference na Activity nebo Context

Nejznámější únik v Androidu — uchovávání statické reference na Activity nebo Context. Typický kód: statické pole Activity, které se při onDestroy() nevynuluje. Dokud statické pole žije, žije celé Activity se svým View stromem, který může zabírat 1–10 MB. To je klasický únik, který LeakCanary nachází jako první.

Řešení: nikdy neuchovávejte Activity nebo Context ve statických polích. Používejte Application Context pro singly, které přežívají Activity. Pokud potřebujete referenci na Activity — použijte WeakReference<Activity>.

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

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

Vnitřní třídy s implicitní referencí

Anonymní třídy a nestatické vnořené třídy implicitně uchovávají referenci na obsahující třídu. Runnable předaný Handleru, který se provádí po onDestroy(), drží celé Activity. Callback Retrofit, který uzavírá Activity, dělá totéž. To je nejzákeřnější typ úniku — implicitní reference není v kódu vidět.

Kotlin object-výrazy a lambdy také zachycují reference na vnější třídu. Dělejte vnořené třídy statické (nebo top-level v Kotlin) a předávejte vnější reference přes WeakReference. Pro lambdy používejte přístup Lifecycle-aware s viewLifecycleOwner.

Neodhlášení posluchači a předplatná

Předplatné systémových služeb bez odhlášení — přímý únik. SensorManager, LocationManager, NotificationListener registrované v onResume() bez volání unregister v onPause() drží Activity. Podobně: RxJava Disposable nepřidaný do CompositeDisposable a korutina spuštěná přes GlobalScope.

Používejte Lifecycle-aware komponenty: observe() s LifecycleOwner se automaticky odhlašuje při onDestroy(). Pro RxJava — viewLifecycleOwner.lifecycle.addObserver s DisposableObserver. Pro korutiny — lifecycleScope.launch() je vázán na životní cyklus.

kotlin
// automatické odhlášení přes Lifecycle
viewModel.userData.observe(viewLifecycleOwner) { data ->
    updateUI(data)
}

// korutiny s lifecycleScope
lifecycleScope.launch {
    viewModel.loadData().collect { render(it) }
}

Bitmap bez recycle

Bitmap zabírá významné množství paměti haldy: jeden FullHD-bitmap — 1920 × 1080 × 4 bajty = 8.3 MB. Pokud se Bitmap vytváří pro každý prvek seznamu a nevolá se recycle() při skrytí, paměť rychle dochází. Na starých verzích Androidu (před 3.0) byl Bitmap uložen v nativní paměti, ale na moderních — na haldě Dalvik/ART, a GC jej může uvolnit pouze pokud neexistuje strong reference.

Používejte Glide nebo Coil pro načítání obrázků — tyto knihovny spravují cache a recycle automaticky. Pokud pracujete přímo s Bitmap, volejte bitmap.recycle() pro velké obrázky, které se již nezobrazují, a používejte inSampleSize pro načítání zmenšených kopií.

Fragment Reference po onDestroyView

Fragment má dva životní cykly: samotného Fragmentu a jeho View. Po onDestroyView() je View strom zničen, ale samotný Fragment může zůstat v paměti, pokud existuje externí reference. Typická chyba — uchovávání reference na Fragment v adaptéru ViewPager nebo v navigačním grafu, která se při zničení nevyčistí.

Nikdy neuchovávejte referenci na Fragment v polích dlouhožijících objektů. Používejte childFragmentManager pro vnořené fragmenty a observe() s LifecycleOwner pro přenos dat mezi nimi. ViewPager2 vyřešil tento problém na úrovni API: FragmentTransactionAdapter správně spravuje životní cyklus.

Jak odhalit únik paměti

Odhalení úniku vyžaduje ověření dvou skutečností: paměť se po expected lifetime nevrací a počet objektů určitého typu roste bez snižování. Proces diagnostiky zahrnuje tři fáze.

První fáze — vizuální kontrola přes Memory Profiler v Android Studio. Otevřete kartu Memory, proveďte cílovou akci (otevřete a zavřete obrazovku), stiskněte GC (Garbage Collection) a sledujte, zda se paměť vrátila na původní úroveň. Pokud po 3–4 cyklech otevření-zavření paměť stabilně roste — došlo k úniku.

Druhá fáze — pořízení Heap Dump. V Memory Profileru stiskněte Dump Java Heap. Získaný soubor .hprof otevřete v Android Studio: uvidíte všechny objekty na haldě s velikostmi a referencemi. Hledejte třídy, jejichž počet by měl být po zavření obrazovky nulový. Například MainActivity s počtem 2 po zavření — zřejmý únik.

Třetí fáze — analýza Retained Size a GC Root. V Android Studio analyzujte Retained Size: kolik paměti se uvolní, pokud tento objekt smažete. Cesta od GC Root k objektu ukazuje, co jej drží: Static field → HashMap → Activity — a vidíte bod úniku. Panel Reference widget zobrazuje všechny držitele objektu.

Nástroje pro vyhledávání úniků

Čtyři nástroje pokrývají vyhledávání úniků od automatické detekce po hloubkovou analýzu Heap Dump.

NástrojMetodaFormát výsledků
LeakCanaryAutomatické monitorováníHeap Dump + stack trace úniku
Android Memory ProfilerRuční monitorováníGraf paměti + Heap Dump
MAT (Eclipse)Hloubková analýzaZpráva Dominator Tree + cesta GC Root
PerfettoSystem-wide trasováníČasová osa + nativní paměť

LeakCanary — must-have pro každý Android projekt. Automaticky detekuje úniky po skončení životního cyklu Activity/Fragment a ukazuje přesné místo úniku se stack trace. Integrace: jeden řádek v build.gradle. LeakCanary 2.x nevyžaduje ruční inicializaci — automaticky registruje Application Watcher.

Jak předcházet únikům paměti

Prevence úniků je zabudována do procesu vývoje prostřednictvím sady pravidel a nástrojů kontrolujících kód v každé fázi.

Pravidlo silných referencí

Nikdy neuchovávejte referenci na Activity, Fragment nebo View ve statickém poli, singletonu nebo dlouhožijícím objektu. Pokud se referenci nelze vyhnout — použijte WeakReference nebo ukládejte data přes ViewModel, který žije přesně tak dlouho, jak je potřeba, a nedrží View přímo.

Lifecycle-aware architektura

ViewModel a LiveData z Android Architecture Components řeší problém životního cyklu na úrovni architektury. ViewModel přežije otočení obrazovky a neobsahuje reference na View. LiveData automaticky odhlašuje observer při onDestroy(). Používejte je místo ručního předplatného systémových služeb.

Code Review se zaměřením na GC Root

Při code review věnujte pozornost: statickým polím s typy Context/View, anonymním třídám, lambdám uzavírajícím Activity, ručním předplatným, RxJava disposable bez composite, ukládání Fragment přes Bundle. V Kotlin navíc kontrolujte korutiny na launch bez vazby na životní cyklus.

Automatická kontrola v CI

LeakCanary může pracovat jako součást testovacího pipeline: spouštějte akceptační testy s LeakCanary a označte build jako neúspěšný, pokud je nalezen únik. To zabraňuje pronikání úniků do produkce. Doplňte kontrolu Android Lint s pravidlem StaticFieldLeak — nachází potenciální úniky na úrovni statické analýzy.

kotlin
// LeakCanary v testech
class LeakTest {
    @Test
    fun activityShouldNotLeak() {
        ActivityScenario.launch(MainActivity::class.java)
            .close()
        LeakAssertions.assertNoLeak() // fail pokud je únik
    }
}

Často kladené otázky

Čím se liší únik paměti od OutOfMemoryError?

Únik je příčina a OutOfMemoryError je následek. Jeden únik nevede k OOM, ale nahromadění desítek úniků vyčerpá Heap. OOM je fatální výjimka a únik je vzor, který k ní v průběhu času vede.

Jak najít únik bez LeakCanary?

Přes Android Memory Profiler: otevírejte a zavírejte obrazovku 5krát, po každém zavření zavolejte GC. Pokud se paměť nevrací na základní úroveň — došlo k úniku. Pořiďte Heap Dump a najděte v seznamu třídu Activity, jejíž počet je větší než 0 po zavření.

Může Kotlin zabránit únikům na úrovni jazyka?

Částečně. Kotlin řeší problém null-safety, ale nespravuje strong references. Korutiny s lifecycleScope a viewModelScope zabraňují únikům z úloh na pozadí a sealed class a data class snižují počet stavů vedoucích k únikům. Hlavní ochrana — architektonické vzory, nikoli funkce jazyka.

Proč LeakCanary nachází únik, který neexistuje?

LeakCanary občas dává false positive: objekt může být dočasně držen systémem (např. InputMethodManager drží poslední View). Zkontrolujte ručně: pokud Retained Size < 1 KB a GC Root je systémová služba, pravděpodobně se jedná o falešný poplach.

Vyskytují se úniky paměti pouze na Androidu?

Ne. Úniky jsou možné na jakékoli platformě s GC: iOS (Swift/Objective-C), Flutter (Dart), webové prohlížeče (JavaScript). Mechanizmy jsou stejné — strong reference od GC Root. Na iOS ARC automaticky spravuje paměť, ale retain cycle mezi objekty vytváří stejný únik.

Shrnutí

  • Memory Leak — objekt, který GC nemůže uvolnit kvůli zapomenuté strong reference
  • Statické reference na Activity a Context — nejčastější příčina úniků
  • Implicitní reference přes anonymní třídy, lambdy a RxJava předplatná jsou zákeřnější než explicitní
  • LeakCanary automaticky nachází úniky a zobrazuje přesný stack trace
  • Lifecycle-aware komponenty (ViewModel, LiveData, lifecycleScope) eliminují třídu úniků
  • Heap Dump a analýza Retained Size — hlavní metoda ruční diagnostiky
  • Prevence zahrnuje code review na strong reference a CI kontrolu LeakCanary

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é