Únik paměti — jeden z nejzákeřnějších problémů v mobilním vývoji. Paměť aplikace neustále roste, dokud nedosáhne limitu nastaveného operačním systémem, poté následuje OutOfMemoryError nebo vynucené ukončení. Podle Square Engineering má přibližně 40% Android aplikací alespoň jeden únik paměti, který lze odhalit pouze při profilování. Rozebereme příčiny a metody prevence růstu paměti.
Hlavní body
Únik paměti (memory leak) — situace, kdy objekt, který aplikace již nepotřebuje, zůstává v heapu, protože na něj existuje aktivní reference z kořenové sady (GC Root). Garbage collector považuje takový objekt za živý a neodstraňuje jej.
Natékání paměti (memory bloat) — širší problém, kdy aplikace spotřebovává více paměti, než je nutné k provádění aktuálních úkolů. Příčiny: nadměrné cachování, duplikace objektů, neoptimální datové struktury a fragmentace heapu.
V Androidu má každá aplikace přidělen omezený heap (obvykle 64-512 MB v závislosti na zařízení a verzi OS). V iOS je omezení méně striktní, ale systém při přiblížení k limitu zasílá varování o paměti.
| Vlastnost | Android | iOS |
|---|---|---|
| Limit heapu | 64-512 MB (závisí na zařízení) | Implicitní (systémový) |
| Garbage collection | ART (Concurrent, Compact) | ARC (Automatic Reference Counting) |
| Mechanismus úniku | Reference GC Root | Retain cycles (silné referenční cykly) |
| Výsledek | OutOfMemoryError | Varování o paměti → ukončení |
Podle Facebook Engineering Blog jsou úniky paměti příčinou ~15% crash hlášení v mobilních aplikacích. V Androidu se k tomu přidávají ANR kvůli častým GC pauzám při nedostatku paměti.
Statická reference na Activity — klasika Android úniků. Pokud statické pole nebo singleton uchovává referenci na Activity, nebude GC sbírána ani po finish(), dokud singleton žije. Activity je těžký objekt obsahující View hierarchii, zdroje a Context.
object LeakHolder {
var activityRef: Activity ?= null // únik: statická reference na Activity
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle ?= null) {
super.onCreate(savedInstanceState)
LeakHolder.activityRef = this // ❌ MainActivity nikdy nebude GC sbírána
}
}
Anonymní třídy a lambdy — implicitně udržují referenci na vnější třídu. Pokud je Runnable nebo Callback předán externí službě a Activity je zničena, objekt anonymní třídy stále visí ve frontě a brání Activity v garbage collection.
V iOS je hlavním problémem retain cycle: dva objekty drží silné reference na sebe navzájem a ARC nemůže vynulovat počítadlo referencí pro žádný z nich. Typický případ: closure silně zachycující self a self držící referenci na closure.
LeakCanary — knihovna od Square pro automatickou detekci úniků v Androidu. Po zničení Activity nebo Fragmentu zkontroluje, zda byl objekt GC sbírán. Pokud ne — provede heap dump a zobrazí trace úniku.
// LeakCanary 2.x — auto-integrace přes Application
class ExampleApplication : Application() {
override fun onCreate() {
super.onCreate()
// LeakCanary se automaticky nainstaluje v debug sestavení
// přes ContentProvider — nastavení s nulovým kódem
}
}
// Vynucené volání kontroly
AppWatcher.objectWatcher.watch(watchedObject, "leak description")
Android Studio Profiler — vestavěný nástroj pro monitorování paměti v reálném čase. Umožňuje nahrát heap dump, najít podezřelé objekty (Retained Size > 1 MB) a sledovat GC root cestu ke každému objektu.
Pro iOS použijte Xcode Memory Graph Debugger. Vizualizuje graf objektů v paměti, ukazuje retain cycles a umožňuje okamžitě odhalit kruhové reference. K dispozici je také Instruments > Allocations pro dlouhodobé monitorování.
WeakReference — základní mechanismus pro reference, které by neměly bránit garbage collection. Pokud GC rozhodne objekt smazat, WeakReference vrátí null. Používá se pro callbacky, listenery a reference na UI komponenty z vlákna na pozadí.
Lifecycle-aware komponenty — architektonický přístup implementovaný v Android Jetpack (Lifecycle, LiveData, Flow, coroutines). Odběry se automaticky ruší při onDestroy, což eliminuje hlavní třídu úniků.
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<List<User>>()
val data: LiveData<List<User>> get() = _data
fun loadData() {
viewModelScope.launch {
val result = repository.fetchData()
_data.postValue(result)
// korutina se automaticky ruší při onCleared()
}
}
}
viewModelScope a lifecycleScope — vestavěné CoroutineScope v Androidu, které se ruší při odpovídající události životního cyklu. Tím se eliminují úniky prostřednictvím korutin — nejčastější scénář v moderním Android vývoji.
Memory Profiler in Android Studio — hlavní nástroj pro sledování heapu. Zobrazuje live allocations, snímky heapu, počet objektů podle typů. Umožňuje nahrát dump a analyzovat jej v MAT (Memory Analyzer Tool) pro nalezení podezřelých objektů.
Eclipse MAT — desktopový analyzátor heap dump. Po načtení HPROF souboru z Android Studio MAT postaví dominator tree, zobrazí retain size každého objektu a nabízí automatickou analýzu podezřelých úniků pomocí Leak Suspects Report.
Xcode Memory Graph — vizuální debugger retain cycles. Po kliknutí na tlačítko Memory Graph Debugger Xcode zastaví aplikaci, postaví kompletní graf objektů v paměti a zvýrazní retain cycles červeně.
| Nástroj | Platforma | Vlastnost |
|---|---|---|
| LeakCanary | Android | Automatická detekce úniků po destroy |
| Memory Profiler | Android Studio | Heap dump + live allocations |
| Eclipse MAT | Android | Dominator tree, Leak Suspects Report |
| Memory Graph | iOS (Xcode) | Visualizátor retain cycles |
Podle Google I/O 2023 aplikace používající LeakCanary v debug sestaveních snižují počet crash souvisejících s pamětí o 30-50% během prvních 2 měsíců po zavedení. Doporučuje se přidat LeakCanary ve fázi onboardingu projektu.
Často kladené otázky
Únik — objekty nedostupné kódu, ale neodstraněné GC kvůli aktivním referencím. Natékání — aplikace drží v paměti objekty, které jsou logicky potřebné, ale v nadměrném množství (např. cache 50 MB u běžící aplikace o velikosti 80 MB). Natékání se léčí architektonicky, únik — správným řízením referencí.
LeakCanary používá ObjectWatcher — po onDestroy() Activity vytvoří WeakReference na Activity a spustí GC. Pokud po 5 sekundách WeakReference není vyčištěna, LeakCanary provede heap dump, analyzuje nejkratší řetězec referencí od GC Root k objektu a zobrazí přesný stack úniku s názvem souboru a řádkem kódu.
Bitmap zabírá paměť mimo Java heap v nativní paměti (native heap). Velikost jednoho Bitmap = šířka × výška × 4 bajty (ARGB_8888). 12 MP fotografie (4000×3000) zabírá 48 MB. Android ne vždy dokáže včas uvolnit nativní paměť, což při nahromadění několika Bitmap vede k OOM i při dostatečném Java heapu.
Retain cycle — situace v ARC, kdy dva objekty drží silné reference navzájem a počítadlo referencí nikdy nedosáhne nuly. Typický příklad: ViewController se silnou referencí na closure a closure silně zachycující self. Řešení: použijte [weak self] nebo [unowned self] v closures.
Velikost heapu závisí na zařízení a verzi Androidu. Pro stará zařízení (API 15-24) — 64-128 MB. Pro moderní (API 25+) — 256-512 MB. Přesnou hodnotu lze získat pomocí ActivityManager.getMemoryClass(). Pro velké aplikace (hry, editory) existuje largeHeap=true v manifestu, poskytující až 1 GB.
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é