Memóriaszivárgás: mi ez, tipikus forgatókönyvek és diagnosztika

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

Memóriaszivárgás (memory leak) — olyan helyzet, amikor az alkalmazás nem szabadítja fel a már nem szükséges objektumok által elfoglalt memóriát. Mobilfejlesztésben ez különösen kritikus: a korlátozott heap és a swap hiánya OutOfMemoryError-hoz és az alkalmazás összeomlásához vezet. A Purdue University (2022) adatai szerint a Google Play-ben lévő Android-alkalmazások 35%-a tartalmaz legalább egy memóriaszivárgást. Tekintsük át a tipikus forgatókönyveket, diagnosztikai eszközöket és megszüntetési módszereket.

Főbb pontok

  • GC Root — belépési pont, amelyen keresztül a szemétgyűjtő meghatározza az élő objektumokat
  • Context szivárgás — az Activity Context singletonnak való átadása a teljes View hierarchia megtartásához vezet
  • Handler postDelayed-del — ha az Activity megsemmisül, a Handler nem engedi, hogy GC-re kerüljön
  • Heap dump — a szivárgások elemzésének elsődleges módszere MAT vagy Android Profiler segítségével
  • SoftReference — a WeakReference alternatívája a memóriahiány esetén automatikusan tisztuló gyorsítótárakhoz

Mi a memóriaszivárgás a mobilalkalmazásokban?

Memóriaszivárgás — olyan helyzet, amikor a lefoglalt memória nem kerül vissza a rendszerbe, miután az objektumra már nincs szüksége a programnak. A szemétgyűjtő az ilyen objektumot élőnek tekinti, mert aktív referencialánc vezet hozzá a GC Root-tól.

Java/Kotlin nyelven a szemétgyűjtő automatikusan működik, de nem tudja megállapítani, hogy egy objektum logikailag nem szükséges, ha technikai referencia létezik rá. A fejlesztőnek explicit módon meg kell szakítania a felesleges kapcsolatokat. Swift/Objective-C nyelven az ARC automatikusan számolja a referenciákat, de a retain cycles blokkolják a számláló nullázását.

A szivárgások fő veszélye a kumulatív hatás. Minden szivárgás kis mennyiségű memóriát fogyaszt, de többszörös képernyőváltásoknál (képernyőforgatás, Activity megnyitása/bezárása) a szivárgások felhalmozódnak, amíg a heap korlátja ki nem merül.

Miben különbözik a szivárgás a felfúvódástól?

Szivárgás — az objektum nem elérhető a kód számára, de a GC nem távolította el. Felfúvódás — az objektum logikailag szükséges, de túlzott mennyiségben van tárolva. Példa felfúvódásra: 100 MB-os képgyorsítótár 30 MB-os munkakészlettel. Mindkét probléma OOM-hoz vezet, de az okok és a kezelési módszerek különbözőek.

Hogyan működik a szemétgyűjtő és miért keletkeznek szivárgások?

ART (Android Runtime) generációs szemétgyűjtést használ concurrent compaction-sel. A memória fiatal generációra (Young), öregre (Old) és hatalmas objektumokra (Large) oszlik. Azok az objektumok, amelyek több GC-ciklust is túléltek, az Old generation-be kerülnek, ahol ritkábban történik gyűjtés — ez felgyorsítja a szokásos ciklusokat.

A GC akkor indul, amikor a heap elér egy bizonyos telítettségi küszöböt (általában 75-85%). A GC alatt az alkalmazás összes szálának futása felfüggesztésre kerül (STW — Stop The World). Minél több az élő objektum, annál hosszabb a szünet. A szivárgások növelik az élő objektumok számát, meghosszabbítva a GC-szüneteket.

A gyűjtő az élő objektumokat a GC Roots-tól indulva bejárva határozza meg: statikus mezők, aktív szálak vermi változói, JNI-referenciák. Bármely objektum, amely e gyökerektől referenciákon keresztül elérhető, élőnek minősül — még akkor is, ha a fejlesztő tudja, hogy már nincs rá szükség.

kotlin
// Példa: statikus gyűjtemény mint GC Root — állandó szivárgás
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 nem blokkolja a GC-t — helyes viselkedés
    }
}

WeakReference megoldja a problémát: a GC figyelmen kívül hagyja a gyenge referenciákat az élő objektumok meghatározásakor. Ha egy objektumra csak gyenge referenciák maradnak, a következő GC-ciklusban összegyűjtésre kerül.

Tipikus szivárgási forgatókönyvek Android és iOS rendszeren

Activity Context — a legelterjedtebb szivárgási forgatókönyv Androidon. Ha egy singleton, statikus mező vagy hosszú életű szolgáltatás tárol referenciát az Activity Context-re, akkor a teljes Activity az összes View-val együtt nem gyűjthető be a GC által. Megoldás: hosszú életű objektumokhoz használj Application Context-et.

Handler és elküldött üzenetek — a Handler.postDelayed(runnable, delay) üzenetet helyez a Main Looper sorába. Ha az Activity a késleltetés lejárta előtt megsemmisül, az üzenet még mindig a sorban van, és megtartja a referenciát a Runnable → névtelen osztály → külső osztály (Activity) útvonalon keresztül.

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) // kötelező: sor törlése
        super.onPause()
    }
}

Inner Classes — a nem statikus belső osztály implicit referenciával rendelkezik a külső osztály példányára. Ha a külső osztály Activity, és a belső osztályt átadják valahova (pl. RecyclerView.Adapter-be), az Activity nem gyűjthető be.

  • TimerTask és ScheduledExecutorService — az Activity megsemmisítése előtt ütemezett feladatok
  • BroadcastReceiver — az onPause/onDestroy-ben regisztrálatlanul hagyott továbbra is tartja a Context-et
  • ViewModel View-referenciával — a ViewModel túléli az Activity-t, a View-re mutató referencia szivárgáshoz vezet
  • Retrofit Call — ha a Call nincs törölve, a válasz a megsemmisült Fragment-be érkezik

Memóriaszivárgás diagnosztikai eszközei

Android Studio Memory Profiler — a beépített eszköz a heap valós idejű monitorozására. Megjeleníti a foglalt memória grafikonját, a foglalások és objektumok számát típusonként. Lehetővé teszi heap dump rögzítését és HPROF formátumba exportálását MAT-ban való elemzéshez.

Eclipse MAT (Memory Analyzer Tool) — asztali heap dump elemző. Automatikusan elkészíti a Leak Suspects Report-ot, amely kiemeli a legnagyobb retained size-dzsel rendelkező objektumokat, és minden gyanús objektumhoz javasol egy lehetséges GC root láncot.

Xcode Memory Graph Debugger — iOS-hez. Felfüggeszti az alkalmazást és vizualizálja az objektumgráfot. A retain cycles pirossal vannak kiemelve, bármely objektumra kattintva megtekinthető a retain count és a referenciák.

EszközFunkciókKomplexitás
Memory ProfilerValós idejű grafikon, heap dump, objektumfoglalás nyomon követéseAlacsony
Eclipse MATDominator fa, szivárgásgyanús esetek, OQL lekérdezésekKözepes
LeakCanaryAutomatikus észlelés, szivárgás nyomvonala az értesítésbenMinimális
Xcode Memory GraphRetain cycles vizuális gráfja, élő objektumok listájaAlacsony

Az Uber Engineering Blog szerint az automatikus memóriaprofilálás (LeakCanary + heap dump elemzés) CI/CD pipeline-ban történő bevezetése 60%-kal csökkenti a memóriával kapcsolatos incidensek számát éles környezetben 3 hónapon belül.

A szivárgások megszüntetésének módszerei

Context cseréje — ha az objektum tovább él, mint az Activity, használj applicationContext-et. Minden hosszú életű objektumnak (singletonok, repository-k, database helperek) Application Context-et kell kapnia, nem Activity Context-et. Kivétel: UI-komponensek, amelyeknek az Activity-specifikus témához vagy erőforrásokhoz kell hozzáférniük.

Lifecycle-aware komponensek — a LifecycleObserver, DefaultLifecycleObserver vagy reactivex használata automatikusan törli a feliratkozásokat az onDestroy-nél. Az Android Jetpack lifecycleScope-ot és viewModelScope-ot biztosít, amelyeket a megfelelő esemény tisztít.

Static inner class — ha a belső osztálynak nincs szüksége hozzáférésre a külső osztály mezőihez, tedd static-ké. A statikus belső osztálynak nincs implicit referenciája a külső osztályra. Ha hozzáférés szükséges, használj WeakReference-t az explicit referenciához.

kotlin
class MyActivity : AppCompatActivity() {

    // ❌ Nem statikus belső osztály — implicit referencia a MyActivity-re
    inner class BadListener : SomeListener {
        override fun onEvent() { /*...*/ }
    }

    // ✅ Statikus belső osztály — nincs implicit referencia
    class GoodListener(private val activityRef: WeakReference<MyActivity>) : SomeListener {
        override fun onEvent() { /*...*/ }
    }
}

iOS-ben használj capture list-eket: [weak self] azokban a closure-ökben, amelyek túlélhetik a létrehozójukat. Delegáltakhoz használj gyenge referenciákat (weak var delegate). Azon closure-ökhöz, amelyek garantáltan csak a self élettartama alatt hívódnak meg, használható [unowned self], de óvatosan — a felszabadított objektum elérése crash-t okoz.

Gyakran ismételt kérdések

Hogyan találhatok szivárgást speciális eszközök nélkül?

Android-ban végezz el néhány váltást a képernyők között (Activity A → B → A → B), és ellenőrizd az adb shell dumpsys meminfo package_name parancsot. Ha a Total PSS folyamatosan nő, és nem tér vissza az eredeti értékre — szivárgás van. iOS-ben hasonlóan: használd a Debug Memory Graph-ot az Xcode-ban a vizuális ellenőrzéshez.

Okozhat-e szivárgást egy Kotlin-korutin?

Igen, ha a CoroutineScope nincs törölve a komponens megsemmisülésekor. A GlobalScope-ban elindított korutin a Activity finish() után is folytatja a végrehajtást. Megoldás: használj viewModelScope-ot (onCleared-ben törlődik) vagy lifecycleScope-ot (onDestroy-ben törlődik). Saját Scope-okhoz hozz létre lifecycle-aware scope-okat LifecycleOwner-en keresztül.

Hogyan befolyásolja a Bitmap a szivárgásokat?

A Bitmap a pixeladatokat natív heap-ben tárolja, nem a Java heap-ben. Ez azt jelenti, hogy a Java GC nem látja a Bitmap valós méretét. Ha a Bitmap-en nem hívják meg a recycle()-t vagy a referencia nem nullázódik, a natív memória nem szabadul fel. Használj BitmapFactory-t inSampleSize-szel a kicsinyített másolatok betöltéséhez, és Glide/Coil-t az automatikus gyorsítótár-kezeléshez.

Mi a szivárgás statikus mezőn keresztül?

A statikus mező — egy GC Root. Addig él, amíg az osztály be van töltve (Android-ben — amíg a Process él). Ha egy statikus mező Activity-re, Bitmap-re, View-ra vagy bármely más nehéz objektumra hivatkozik, ez az objektum soha nem lesz begyűjtve a GC által. A statikus mező — örök referencia. Megoldás: csak WeakReference-t tárolj, vagy nullázd a statikus mezőt az onDestroy-ban.

Hogyan kerülhetem el a szivárgásokat iOS-ben ARC-kel?

Az ARC automatikusan felszabadítja az objektumokat, amikor az erős referenciák számlálója nullára csökken. A retain cycle — az egyetlen szivárgási mód ARC-ben. Mindig használj weak-et a parent→child referenciákhoz, ahol a child-nak túl kell élnie a parent-et (delegáltak, data source). A closure-ökhöz használj capture list-et: [weak self], és ellenőrizd a self-et nil-ra a closure-on belül.

Összefoglalás

  • Memóriaszivárgás — az objektum nem elérhető a kód számára, de a GC nem távolította el, mert aktív referencia van rá a GC Root-tól
  • GC Roots magukban foglalják a statikus mezőket, vermi változókat és JNI-referenciákat; bármely objektum, amely elérhető belőlük, él
  • Context szivárgás — a legelterjedtebb probléma Androidban: Activity Context átadása singletonnak vagy statikus mezőnek
  • Handler és Inner Class — a második leggyakoribb ok: a Looper sorában lévő nem törölt üzenetek megtartják a referenciát az Activity-re
  • LeakCanary — a szabványos automatikus észlelő eszköz; heap dump-ot készít és megmutatja a pontos GC root láncot
  • lifecycleScope és viewModelScope megoldja a korutinákon keresztüli szivárgás problémáját — automatikus törlés destroy-kor
  • Profilozd a memóriát CI/CD-ben: a LeakCanary debug módban + heap dump elemzés a tesztfuttatásban blokkolja a merge-t új szivárgásoknál

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