Memóriaszivárgás mobil alkalmazásokban — mi ez, okai és felderítési módszerek

Szerző: IT Sectr Megjelenés: 2026-03-29 Olvasási idő: 9 perc

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

  • Memory Leak — az objektum a memóriában marad, bár nincs aktív referencia rá az alkalmazás logikájából
  • Statikus referenciák Activity-re vagy Context-re — a leggyakoribb szivárgási ok Androidon
  • LeakCanary — a szivárgások automatikus észlelésének szabványos eszköze Androidon
  • WeakReference és Application Context — a szivárgás megelőzésének alapvető technikái
  • Lifecycle-aware komponensek megszüntetik a feliratkozásokkal kapcsolatos szivárgások egész osztályát

Mi a memóriaszivárgás

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.

Tipikus szivárgástípusok mobil alkalmazásokban

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

Statikus referencia Activity-re vagy Context-re

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

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

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

Belső osztályok implicit referenciával

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.

Le nem iratkozott figyelők és feliratkozások

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.

kotlin
// automatikus leiratkozás Lifecycle-en keresztül
viewModel.userData.observe(viewLifecycleOwner) { data ->
    updateUI(data)
}

// korutinok lifecycleScope-al
lifecycleScope.launch {
    viewModel.loadData().collect { render(it) }
}

Bitmap recycle nélkül

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.

Fragment referencia onDestroyView után

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.

Hogyan észlelhető a memóriaszivárgás

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

Eszközök a szivárgások felderítésére

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özMódszerEredményformátum
LeakCanaryAutomatikus monitorozásHeap Dump + szivárgás stack trace
Android Memory ProfilerKézi monitorozásMemóriagrafikon + Heap Dump
MAT (Eclipse)Mélyreható elemzésDominator Tree jelentés + GC Root útvonal
PerfettoRendszerszintű nyomkövetésIdő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.

Hogyan előzhetők meg a memóriaszivárgások

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.

Erős referenciák szabálya

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.

Lifecycle-aware architektúra

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 GC Root fókusszal

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.

Automatikus ellenőrzés CI-ben

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.

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

Miben különbözik a memóriaszivárgás az OutOfMemoryError-tól?

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.

Hogyan található szivárgás LeakCanary nélkül?

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.

Megakadályozhatja-e a Kotlin a szivárgásokat nyelvi szinten?

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.

Miért talál a LeakCanary olyan szivárgást, ami nincs?

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

Csak Androidon fordulnak elő memóriaszivárgások?

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

  • Memory Leak — objektum, amelyet a GC nem tud felszabadítani egy elfelejtett strong reference miatt
  • Statikus referenciák Activity-re és Context-re — a szivárgások leggyakoribb oka
  • Implicit referenciák anonim osztályokon, lambdákon és RxJava feliratkozásokon keresztül alattomosabbak, mint az explicit
  • LeakCanary automatikusan megtalálja a szivárgásokat és megmutatja a pontos stack trace-t
  • Lifecycle-aware komponensek (ViewModel, LiveData, lifecycleScope) megszüntetik a szivárgások egy osztályát
  • Heap Dump és Retained Size elemzés — a kézi diagnosztika fő módszere
  • Megelőzés magában foglalja a code review-t a strong reference-ekre és a CI ellenőrzést LeakCanary-val

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.

Projekt megbeszélése

Olvassa el is