Scurgerea de memorie în aplicațiile mobile — ce este, cauze și metode de detectare

Autor: IT Sectr Publicat: 2026-03-29 Timp de citire: 9 min

Scurgerea de memorie (Memory Leak) — situația în care aplicația păstrează referințe către obiecte care nu mai sunt necesare, împiedicând garbage collector-ul să elibereze occupied memory. Conform datelor LeakCanary, chiar și în aplicații bine scrise se întâlnesc 3–5 scurgeri la 10 000 de linii de cod. Fiecare scurgere reduce treptat memoria disponibilă, ducând la încetiniri și OutOfMemoryError.

Puncte cheie

  • Memory Leak — obiectul rămâne în memorie, deși nu există referințe active la el din logica aplicației
  • Referințe statice la Activity sau Context — cea mai frecventă cauză a scurgerilor pe Android
  • LeakCanary — instrumentul standard pentru detectarea automată a scurgerilor pe Android
  • WeakReference și Application Context — tehnici de bază pentru prevenirea scurgerilor
  • Componentele Lifecycle-aware elimină o întreagă clasă de scurgeri legate de abonamente

Ce este scurgerea de memorie

Scurgerea de memorie (Memory Leak) — este situația în care un obiect rămâne accesibil printr-un lanț de referințe puternice (Strong Reference), deși logic nu mai este necesar aplicației. Garbage collector-ul (GC) consideră un astfel de obiect viu și nu eliberează memoria ocupată de acesta. Ca rezultat, memoria heap disponibilă se reduce constant, iar frecvența pauzelor GC crește.

Spre deosebire de limbajele cu gestionare manuală a memoriei (C, C++), în Java/Kotlin scurgerea nu este un free() uitat, ci o referință uitată. Cât timp există o strong reference de la obiectul rădăcină (GC Root) până la obiectul scurs, GC îl consideră necesar. GC Root-uri tipice: câmpuri statice, fire active, stiva de apeluri, referințe globale JNI.

Pericolul scurgerilor este efectul lor cumulativ. O scurgere de 100 KB nu se observă, dar 100 de astfel de scurgeri ocupă 10 MB, iar aplicația începe să încetinească din cauza GC-urilor frecvente. Masa critică de scurgeri duce la OutOfMemoryError și crash-ul aplicației. Simptomele scurgerii: creșterea constantă a consumului de memorie pe graficul Profiler, pauze frecvente GC cu STW (Stop The World) și scăderea performanței UI.

Tipuri comune de scurgeri în aplicațiile mobile

Cinci tipuri de scurgeri acoperă 95% din cazurile în dezvoltarea mobilă. Fiecare are cauza și modelul său caracteristic în cod.

Referință statică la Activity sau Context

Cea mai cunoscută scurgere pe Android — păstrarea unei referințe statice la Activity sau Context. Cod tipic: un câmp static Activity care nu se anulează la onDestroy(). Cât timp câmpul static este viu, întregul Activity cu arborele său View, care poate ocupa 1–10 MB, rămâne viu. Aceasta este o scurgere clasică pe care LeakCanary o găsește în primul rând.

Soluție: nu păstrați niciodată Activity sau Context în câmpuri statice. Folosiți Application Context pentru singleton-uri care supraviețuiesc Activity-ului. Dacă aveți nevoie de o referință la Activity — folosiți WeakReference<Activity>.

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

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

Clase interne cu referință implicită

Clasele anonime și clasele imbricate non-statice păstrează implicit o referință către clasa care le conține. Un Runnable transmis către Handler care se execută după onDestroy() menține întregul Activity. Un callback Retrofit care închide Activity face același lucru. Acesta este cel mai perfid tip de scurgere — referința implicită nu este vizibilă în cod.

Expresiile object și lambda-urile din Kotlin capturează și ele referințe către clasa externă. Faceți clasele imbricate statice (sau top-level în Kotlin) și transmiteți referințele externe prin WeakReference. Pentru lambda-uri, folosiți abordarea Lifecycle-aware cu viewLifecycleOwner.

Listenere și abonamente nedezabonate

Abonarea la servicii sistem fără dezabonare — o scurgere directă. SensorManager, LocationManager, NotificationListener înregistrate în onResume() fără apelarea unregister în onPause() mențin Activity-ul. Similar: RxJava Disposable neadăugat la CompositeDisposable și corutină lansată prin GlobalScope.

Folosiți componente Lifecycle-aware: observe() cu LifecycleOwner se dezabonează automat la onDestroy(). Pentru RxJava — viewLifecycleOwner.lifecycle.addObserver cu DisposableObserver. Pentru corutine — lifecycleScope.launch() este legat de ciclul de viață.

kotlin
// dezabonare automată prin Lifecycle
viewModel.userData.observe(viewLifecycleOwner) { data ->
    updateUI(data)
}

// corutine cu lifecycleScope
lifecycleScope.launch {
    viewModel.loadData().collect { render(it) }
}

Bitmap fără recycle

Bitmap ocupă o cantitate semnificativă de memorie heap: un FullHD-bitmap — 1920 × 1080 × 4 biți = 8.3 MB. Dacă se creează un Bitmap pentru fiecare element al listei și nu se apelează recycle() la ascundere, memoria se epuizează rapid. Pe versiunile vechi de Android (înainte de 3.0), Bitmap era stocat în memoria native, dar pe cele moderne — în heap-ul Dalvik/ART, iar GC îl poate elibera doar dacă nu există strong reference.

Folosiți Glide sau Coil pentru încărcarea imaginilor — aceste biblioteci gestionează caching-ul și recycle-ul automat. Dacă lucrați direct cu Bitmap, apelați bitmap.recycle() pentru imaginile mari care nu mai sunt afișate și folosiți inSampleSize pentru încărcarea copiilor reduse.

Referință la Fragment după onDestroyView

Fragment are două cicluri de viață: al Fragmentului propriu-zis și al View-ului său. După onDestroyView(), arborele View este distrus, dar Fragmentul însuși poate rămâne în memorie dacă există o referință externă. Eroare tipică — păstrarea referinței la Fragment în adaptorul ViewPager sau în graficul de navigare care nu se curăță la distrugere.

Nu păstrați niciodată referința la Fragment în câmpurile obiectelor longevive. Folosiți childFragmentManager pentru fragmente imbricate și observe() cu LifecycleOwner pentru transmiterea datelor între ele. ViewPager2 a rezolvat această problemă la nivel de API: FragmentTransactionAdapter gestionează corect ciclul de viață.

Cum să depistezi scurgerea de memorie

Depistarea scurgerii necesită verificarea a două fapte: memoria nu revine după expected lifetime și numărul de obiecte de un anumit tip crește fără a scădea. Procesul de diagnosticare include trei etape.

Prima etapă — verificarea vizuală prin Memory Profiler în Android Studio. Deschideți fila Memory, executați acțiunea țintă (deschideți și închideți ecranul), apăsați GC (Garbage Collection) și urmăriți dacă memoria revine la nivelul inițial. Dacă după 3–4 cicluri de deschidere-închidere memoria crește constant — există o scurgere.

A doua etapă — realizarea unui Heap Dump. În Memory Profiler apăsați Dump Java Heap. Fișierul .hprof obținut deschideți-l în Android Studio: veți vedea toate obiectele din heap cu dimensiuni și referințe. Căutați clasele al căror număr ar trebui să fie zero după închiderea ecranului. De exemplu, MainActivity cu numărul 2 după închidere — o scurgere evidentă.

A treia etapă — analiza Retained Size și GC Root. În Android Studio analizați Retained Size: câtă memorie se va elibera dacă ștergeți acest obiect. Calea de la GC Root la obiect arată ce îl menține: Static field → HashMap → Activity — și vedeți punctul de scurgere. Panoul Reference widget arată toți deținătorii obiectului.

Instrumente pentru detectarea scurgerilor

Patru instrumente acoperă detectarea scurgerilor de la depistarea automată până la analiza profundă a Heap Dump.

InstrumentMetodăFormat rezultate
LeakCanaryMonitorizare automatăHeap Dump + stack trace al scurgerii
Android Memory ProfilerMonitorizare manualăGrafic memorie + Heap Dump
MAT (Eclipse)Analiză profundăRaport Dominator Tree + cale GC Root
PerfettoTrasare system-wideLinie temporală + memorie native

LeakCanary — must-have pentru orice proiect Android. Detectează automat scurgerile după încheierea ciclului de viață al Activity/Fragment și arată locul exact al scurgerii cu stack trace. Integrare: o singură linie în build.gradle. LeakCanary 2.x nu necesită inițializare manuală — înregistrează automat Application Watcher.

Cum să previi scurgerile de memorie

Prevenirea scurgerilor este integrată în procesul de dezvoltare printr-un set de reguli și instrumente care verifică codul în fiecare etapă.

Regula referințelor puternice

Niciodată nu păstrați referința la Activity, Fragment sau View într-un câmp static, singleton sau obiect longeviv. Dacă nu se poate evita referința — folosiți WeakReference sau stocați datele prin ViewModel, care trăiește exact atât cât trebuie și nu menține View-ul direct.

Arhitectură Lifecycle-aware

ViewModel și LiveData din Android Architecture Components rezolvă problema ciclului de viață la nivel de arhitectură. ViewModel supraviețuiește rotației ecranului și nu conține referințe către View. LiveData dezabonează automat observer-ul la onDestroy(). Folosiți-le în locul abonării manuale la servicii sistem.

Code Review cu accent pe GC Root

În code review acordați atenție: câmpurilor statice cu tipuri Context/View, claselor anonime, lambda-urilor care închid Activity, abonamentelor manuale, RxJava disposable fără composite, stocării Fragment prin Bundle. În Kotlin verificați suplimentar corutinele pentru launch fără legătură la ciclul de viață.

Verificare automată în CI

LeakCanary poate funcționa ca parte a pipeline-ului de testare: rulați teste de acceptare cu LeakCanary și marcați build-ul ca eșuat dacă se găsește o scurgere. Aceasta previne intrarea scurgerilor în producție. Completați verificarea cu Android Lint cu regula StaticFieldLeak — aceasta găsește scurgeri potențiale la nivel de analiză statică.

kotlin
// LeakCanary în teste
class LeakTest {
    @Test
    fun activityShouldNotLeak() {
        ActivityScenario.launch(MainActivity::class.java)
            .close()
        LeakAssertions.assertNoLeak() // fail dacă există scurgere
    }
}

Întrebări frecvente

Cu ce se deosebește scurgerea de memorie de OutOfMemoryError?

Scurgerea este cauza, iar OutOfMemoryError este efectul. O singură scurgere nu duce la OOM, dar acumularea a zeci de scurgeri epuizează heap-ul. OOM este o excepție fatală, iar scurgerea este un model care duce la ea în timp.

Cum să găsești o scurgere fără LeakCanary?

Prin Android Memory Profiler: deschideți și închideți ecranul de 5 ori, după fiecare închidere apelați GC. Dacă memoria nu revine la nivelul de bază — există o scurgere. Faceți un Heap Dump și găsiți în listă clasa Activity a cărei cantitate este mai mare de 0 după închidere.

Poate Kotlin să prevină scurgerile la nivel de limbaj?

Parțial. Kotlin rezolvă problema null-safety, dar nu gestionează strong references. Corutinele cu lifecycleScope și viewModelScope previn scurgerile din sarcinile de fundal, iar sealed class și data class reduc numărul de stări care duc la scurgeri. Protecția principală o constituie modelele arhitecturale, nu funcțiile limbajului.

De ce LeakCanary găsește o scurgere care nu există?

LeakCanary uneori dă false positive: un obiect poate fi reținut temporar de sistem (de exemplu, InputMethodManager reține ultimul View). Verificați manual: dacă Retained Size < 1 KB și GC Root este un serviciu de sistem, probabil este o alarmă falsă.

Scurgerile de memorie apar doar pe Android?

Nu. Scurgerile sunt posibile pe orice platformă cu GC: iOS (Swift/Objective-C), Flutter (Dart), browsere web (JavaScript). Mecanismele sunt aceleași — strong reference de la GC Root. Pe iOS, ARC gestionează automat memoria, dar retain cycle între obiecte creează aceeași scurgere.

Concluzii

  • Memory Leak — un obiect pe care GC nu-l poate elibera din cauza unei strong reference uitate
  • Referințele statice la Activity și Context — cea mai frecventă cauză a scurgerilor
  • Referințele implicite prin clase anonime, lambda-uri și abonamente RxJava sunt mai perfide decât cele explicite
  • LeakCanary găsește automat scurgeri și arată stack trace-ul exact
  • Componentele Lifecycle-aware (ViewModel, LiveData, lifecycleScope) elimină o clasă de scurgeri
  • Heap Dump și analiza Retained Size — metoda principală de diagnosticare manuală
  • Prevenirea include code review pe strong reference și verificare CI cu LeakCanary

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și