Memóriaszivárgás (Memory Leak) — olyan helyzet, amikor az alkalmazás olyan objektumokra tart referenciákat, amelyekre már nincs szükség, megakadályozva a garbage collectort az occupied memory felszabadításában. A LeakCanary adatai szerint még a jól megírt alkalmazásokban is 3–5 szivárgás található 10 000 kódsoronként. Minden szivárgás fokozatosan csökkenti a rendelkezésre álló memóriát, lassuláshoz és OutOfMemoryError-hoz vezetve.
Főbb pontok
Memóriaszivárgás (Memory Leak) — olyan helyzet, amikor egy objektum erős referenciák (Strong Reference) láncán keresztül elérhető marad, bár logikailag már nincs szüksége rá az alkalmazásnak. A garbage collector (GC) az ilyen objektumot élőnek tekinti és nem szabadítja fel az általa elfoglalt memóriát. Ennek eredményeként a rendelkezésre álló heap memória folyamatosan csökken, a GC szünetek gyakorisága pedig nő.
Ellentétben a kézi memóriakezelésű nyelvekkel (C, C++), Java/Kotlin esetén a szivárgás nem egy elfelejtett free(), hanem egy elfelejtett referencia. Amíg létezik strong reference a gyökérobjektumtól (GC Root) a szivárgó objektumig, a GC azt szükségesnek tekinti. Tipikus GC Root-ok: statikus mezők, aktív szálak, hívási verem, JNI-globális referenciák.
A szivárgások veszélye a kumulatív hatásuk. Egy 100 KB-os szivárgás nem észrevehető, de 100 ilyen szivárgás 10 MB-ot foglal, és az alkalmazás lassulni kezd a gyakori GC miatt. A szivárgások kritikus tömege OutOfMemoryError-hoz és az alkalmazás összeomlásához vezet. A szivárgás tünetei: a memóriafogyasztás folyamatos növekedése a Profiler grafikonján, gyakori GC szünetek STW-vel (Stop The World) és a UI teljesítményének csökkenése.
Öt típusú szivárgás fedi le az esetek 95%-át a mobil fejlesztésben. Mindegyiknek megvan a maga oka és jellemző mintája a kódban.
A legismertebb szivárgás Androidon — statikus referencia tárolása Activity-re vagy Context-re. Tipikus kód: egy statikus Activity mező, amely nem nullázódik onDestroy() esetén. Amíg a statikus mező él, az egész Activity a View fájával együtt, amely 1–10 MB-ot foglalhat, életben marad. Ez egy klasszikus szivárgás, amelyet a LeakCanary elsőként talál meg.
Megoldás: soha ne tároljon Activity vagy Context statikus mezőkben. Használjon Application Context-et az Activity-nél tovább élő singletonokhoz. Ha Activity-re van szüksége — használjon WeakReference<Activity>.
object MySingleton {
private var weakActivity: WeakReference<Activity>? = null
fun attach(activity: Activity) {
weakActivity = WeakReference(activity)
}
}
Anonim osztályok és nem statikus beágyazott osztályok implicit módon tárolnak referenciát a tartalmazó osztályra. Egy Runnable, amelyet a Handler-nek adunk át és onDestroy() után hajtódik végre, az egész Activity-t tartja. Egy Retrofit callback, amely lezárja az Activity-t, ugyanezt teszi. Ez a legravaszabb szivárgástípus — az implicit referencia nem látható a kódban.
A Kotlin object-kifejezések és lambda-kifejezések is elkapják a külső osztályra mutató referenciákat. Tegye a beágyazott osztályokat statikussá (vagy top-level-é Kotlinban), és adja át a külső referenciákat WeakReference-en keresztül. Lambda-ok esetén használja a Lifecycle-aware megközelítést viewLifecycleOwner-rel.
Feliratkozás rendszerszolgáltatásokra leiratkozás nélkül — közvetlen szivárgás. onResume()-ban regisztrált SensorManager, LocationManager, NotificationListener az onPause()-ban való unregister hívása nélkül tartja az Activity-t. Hasonló: CompositeDisposable-hoz nem adott RxJava Disposable és GlobalScope-on keresztül indított korutin.
Használjon Lifecycle-aware komponenseket: az observe() LifecycleOwner-rel automatikusan leiratkozik onDestroy() esetén. RxJava-hoz — viewLifecycleOwner.lifecycle.addObserver DisposableObserver-rel. Korutinokhoz — a lifecycleScope.launch() az életciklushoz van kötve.
// automatikus leiratkozás Lifecycle-en keresztül
viewModel.userData.observe(viewLifecycleOwner) { data ->
updateUI(data)
}
// korutinok lifecycleScope-al
lifecycleScope.launch {
viewModel.loadData().collect { render(it) }
}
A Bitmap jelentős mennyiségű heap memóriát foglal: egy FullHD-bitmap — 1920 × 1080 × 4 bájt = 8.3 MB. Ha minden listaelemhez létrejön egy Bitmap, és elrejtéskor nem hívódik meg a recycle(), a memória gyorsan kimerül. Régebbi Android verziókon (3.0 előtt) a Bitmap natív memóriában tárolódott, de modernekben — a Dalvik/ART heap-ben, és a GC csak akkor szabadíthatja fel, ha nincs strong reference.
Használjon Glide vagy Coil képek betöltéséhez — ezek a könyvtárak automatikusan kezelik a gyorsítótárazást és a recycle-t. Ha közvetlenül Bitmap-pel dolgozik, hívja meg a bitmap.recycle()-et a már nem megjelenített nagy képekhez, és használja az inSampleSize-t kicsinyített másolatok betöltéséhez.
A Fragment két életciklussal rendelkezik: magáéval a Fragment-é és a View-éval. Az onDestroyView() után a View fa megsemmisül, de maga a Fragment a memóriában maradhat, ha van külső referencia. Tipikus hiba — a Fragment-re mutató referencia tárolása a ViewPager adapterben vagy a navigációs gráfban, amely nem tisztul meg a megsemmisítéskor.
Soha ne tároljon referenciát Fragment-re hosszú életű objektumok mezőiben. Használjon childFragmentManager-t beágyazott fragmentekhez és observe()-t LifecycleOwner-rel a köztük lévő adatátvitelhez. A ViewPager2 API szinten oldotta meg ezt a problémát: a FragmentTransactionAdapter helyesen kezeli az életciklust.
Észlelés két tény ellenőrzését igényli: a memória nem tér vissza az expected lifetime után, és egy bizonyos típusú objektumok száma csökkenés nélkül nő. A diagnosztikai folyamat három szakaszból áll.
Első szakasz — vizuális ellenőrzés a Memory Profileren keresztül Android Studio-ban. Nyissa meg a Memory fület, hajtsa végre a célműveletet (nyissa ki és csukja be a képernyőt), nyomja meg a GC-t (Garbage Collection), és nézze meg, hogy a memória visszatért-e az eredeti szintre. Ha 3–4 nyitás-zárás ciklus után a memória folyamatosan nő — szivárgás van.
Második szakasz — Heap Dump készítése. A Memory Profiler-ben nyomja meg a Dump Java Heap gombot. A kapott .hprof fájlt nyissa meg Android Studio-ban: látni fogja az összes objektumot a heap-ben méretekkel és referenciákkal. Keresse azokat az osztályokat, amelyek számának nullának kellene lennie a képernyő bezárása után. Például egy MainActivity 2-es számmal bezárás után — egyértelmű szivárgás.
Harmadik szakasz — a Retained Size és a GC Root elemzése. Az Android Studio-ban elemezze a Retained Size-t: mennyi memória szabadul fel, ha törli ezt az objektumot. A GC Root-tól az objektumig vezető út mutatja, mi tartja: Static field → HashMap → Activity — és látja a szivárgási pontot. A Reference widget panel az objektum összes tartóját mutatja.
Négy eszköz fedi le a szivárgások felderítését az automatikus észlelésétől a mélyreható Heap Dump elemzésig.
| Eszköz | Módszer | Eredményformátum |
|---|---|---|
| LeakCanary | Automatikus monitorozás | Heap Dump + szivárgás stack trace |
| Android Memory Profiler | Kézi monitorozás | Memóriagrafikon + Heap Dump |
| MAT (Eclipse) | Mélyreható elemzés | Dominator Tree jelentés + GC Root útvonal |
| Perfetto | Rendszerszintű nyomkövetés | Idővonal + natív memória |
LeakCanary — must-have minden Android projekthez. Automatikusan észleli a szivárgásokat az Activity/Fragment életciklusának végén, és megmutatja a szivárgás pontos helyét a stack trace-szel. Integráció: egy sor a build.gradle-ben. A LeakCanary 2.x nem igényel kézi inicializálást — automatikusan regisztrálja az Application Watcher-t.
Megelőzés a fejlesztési folyamatba épül szabályok és eszközök segítségével, amelyek minden szakaszban ellenőrzik a kódot.
Soha ne tároljon referenciát Activity-re, Fragment-re vagy View-ra statikus mezőben, singletonban vagy hosszú életű objektumban. Ha a referencia elkerülhetetlen — használjon WeakReference-t, vagy tárolja az adatokat ViewModel-en keresztül, amely pontosan addig él, amíg szükséges, és nem tartja közvetlenül a View-t.
ViewModel és LiveData az Android Architecture Components-ből architektúra szinten oldja meg az életciklus problémáját. A ViewModel túléli a képernyő elforgatását és nem tartalmaz referenciákat View-ra. A LiveData automatikusan leiratkoztatja a observer-t onDestroy() esetén. Használja őket a rendszerszolgáltatásokra való kézi feliratkozás helyett.
Code Review során figyeljen a következőkre: statikus mezők Context/View típusokkal, anonim osztályok, Activity-t lezáró lambdák, kézi feliratkozások, RxJava disposable composite nélkül, Fragment tárolása Bundle-en keresztül. Kotlin-ban emellett ellenőrizze a korutinokat launch-ra életciklushoz való kötés nélkül.
LeakCanary a teszt pipeline részeként működhet: futtasson elfogadási teszteket LeakCanary-val, és jelölje meg a build-et sikertelennek, ha szivárgást talál. Ez megakadályozza a szivárgások élesbe kerülését. Egészítse ki az ellenőrzést Android Lint-tel a StaticFieldLeak szabállyal — ez potenciális szivárgásokat talál a statikus elemzés szintjén.
// LeakCanary tesztekben
class LeakTest {
@Test
fun activityShouldNotLeak() {
ActivityScenario.launch(MainActivity::class.java)
.close()
LeakAssertions.assertNoLeak() // fail ha szivárgás van
}
}
Gyakran Ismételt Kérdések
A szivárgás az ok, az OutOfMemoryError pedig a következmény. Egy szivárgás nem vezet OOM-hez, de tucatnyi szivárgás felhalmozódása kimeríti a Heap-et. Az OOM végzetes kivétel, a szivárgás pedig egy minta, amely idővel odavezet.
Android Memory Profiler segítségével: nyissa ki és csukja be a képernyőt 5-ször, minden bezárás után hívja meg a GC-t. Ha a memória nem tér vissza az alapszintre — szivárgás van. Készítsen Heap Dump-ot, és keresse meg a listában azt az Activity osztályt, amelynek száma nagyobb, mint 0 a bezárás után.
Részben. A Kotlin megoldja a null-biztonság problémáját, de nem kezeli a strong reference-eket. A lifecycleScope és viewModelScope korutinok megakadályozzák a háttérfeladatokból eredő szivárgásokat, a sealed class és data class pedig csökkenti a szivárgáshoz vezető állapotok számát. A fő védelem — architekturális minták, nem nyelvi funkciók.
LeakCanary néha hamis pozitív (false positive) eredményt ad: egy objektumot ideiglenesen tarthat a rendszer (pl. az InputMethodManager tartja az utolsó View-t). Ellenőrizze kézzel: ha a Retained Size < 1 KB és a GC Root rendszerszolgáltatás, akkor valószínűleg hamis riasztásról van szó.
Nem. Szivárgások bármely GC-vel rendelkező platformon lehetségesek: iOS (Swift/Objective-C), Flutter (Dart), webböngészők (JavaScript). A mechanizmusok ugyanazok — strong reference a GC Root-tól. Az iOS-en az ARC automatikusan kezeli a memóriát, de az objektumok közötti retain cycle ugyanazt a szivárgást hozza létre.
Összegzés
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is