Scurgerea de memorie — una dintre cele mai perfide probleme în dezvoltarea mobilă. Memoria aplicației crește neîncetat până când atinge limita stabilită de sistemul de operare, după care urmează OutOfMemoryError sau terminarea forțată. Conform Square Engineering, aproximativ 40% dintre aplicațiile Android au cel puțin o scurgere de memorie care poate fi detectată doar la profilare. Să analizăm cauzele și metodele de prevenire a creșterii memoriei.
Principalele puncte
Scurgerea de memorie (memory leak) — situația în care un obiect care nu mai este necesar aplicației continuă să fie reținut în heap deoarece există o referință activă din setul rădăcină (GC Root). Colectorul de gunoi consideră un astfel de obiect viu și nu îl șterge.
Umflarea memoriei (memory bloat) — o problemă mai largă când aplicația consumă mai multă memorie decât este necesar pentru îndeplinirea sarcinilor curente. Cauze: cache excesiv, duplicarea obiectelor, structuri de date neoptimale și fragmentarea heap-ului.
În Android fiecărei aplicații i se alocă un heap limitat (de obicei 64-512 MB în funcție de dispozitiv și versiunea sistemului de operare). În iOS limitarea este mai puțin strictă, dar sistemul trimite un avertisment de memorie la apropierea de limită.
| Caracteristică | Android | iOS |
|---|---|---|
| Limita heap | 64-512 MB (depinde de dispozitiv) | Implicită (sistem) |
| Colectarea gunoiului | ART (Concurrent, Compact) | ARC (Automatic Reference Counting) |
| Mecanism de scurgere | Referințe GC Root | Cicluri retain (cicluri de referințe puternice) |
| Rezultat | OutOfMemoryError | Avertisment de memorie → terminare |
Conform Facebook Engineering Blog, scurgerile de memorie sunt cauza a ~15% din rapoartele de crash în aplicațiile mobile. În Android se adaugă ANR din cauza pauzelor frecvente GC la lipsa de memorie.
Referința statică la Activity — clasica scurgerilor Android. Dacă un câmp static sau un singleton stochează o referință la Activity, aceasta nu va fi colectată de GC nici după finish(), cât timp singletonul este viu. Activity este un obiect greu care conține ierarhia View, resurse și Context.
object LeakHolder {
var activityRef: Activity ?= null // scurgere: referință statică la Activity
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle ?= null) {
super.onCreate(savedInstanceState)
LeakHolder.activityRef = this // ❌ MainActivity nu va fi niciodată colectat de GC
}
}
Clasele anonime și lambda — rețin implicit o referință la clasa exterioară. Dacă un Runnable sau Callback este transmis unui serviciu extern, iar Activity este distrus, obiectul clasei anonime încă atârnă în coadă și nu permite Activity să fie colectat de GC.
În iOS problema principală sunt retain cycles: două obiecte rețin referințe puternice unul la altul, iar ARC nu poate zero contorul de referințe pentru niciunul. Caz tipic: un closure care capturează self puternic, iar self reține referința la closure.
LeakCanary — biblioteca de la Square pentru detectarea automată a scurgerilor în Android. După distrugerea Activity sau Fragment verifică dacă obiectul a fost colectat de GC. Dacă nu — face un heap dump și arată trace-ul scurgerii.
// LeakCanary 2.x — auto-integrare prin Application
class ExampleApplication : Application() {
override fun onCreate() {
super.onCreate()
// LeakCanary se instalează automat în build-ul debug
// prin ContentProvider — configurare zero cod
}
}
// Apel de verificare forțată
AppWatcher.objectWatcher.watch(watchedObject, "leak description")
Android Studio Profiler — instrumentul încorporat pentru monitorizarea memoriei în timp real. Permite înregistrarea heap dump, găsirea obiectelor suspecte (Retained Size > 1 MB) și urmărirea căii GC root până la fiecare obiect.
Pentru iOS utilizați Xcode Memory Graph Debugger. Acesta vizualizează graful obiectelor în memorie, arată retain cycles și permite detectarea instantanee a referințelor circulare. De asemenea, este disponibil Instruments > Allocations pentru monitorizare pe termen lung.
WeakReference — mecanismul de bază pentru referințele care nu ar trebui să împiedice colectarea gunoiului. Dacă GC decide să șteargă obiectul, WeakReference returnează null. Folosit pentru callback-uri, listeneri și referințe la componente UI din firele de fundal.
Componentele Lifecycle-aware — abordarea arhitecturală implementată în Android Jetpack (Lifecycle, LiveData, Flow, coroutines). Abonamentele sunt anulate automat la onDestroy, ceea ce elimină clasa principală de scurgeri.
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)
// corutina se anulează automat la onCleared()
}
}
}
viewModelScope și lifecycleScope — CoroutineScope încorporate în Android care sunt anulate la evenimentul corespunzător al ciclului de viață. Aceasta elimină scurgerile prin corutine — cel mai frecvent scenariu în dezvoltarea modernă Android.
Memory Profiler in Android Studio — instrumentul principal pentru monitorizarea heap-ului. Afișează alocări live, instantanee heap, numărul de obiecte pe tipuri. Permite înregistrarea unui dump și analiza acestuia în MAT (Memory Analyzer Tool) pentru găsirea obiectelor suspecte.
Eclipse MAT — analizor desktop de heap dump. După încărcarea fișierului HPROF din Android Studio, MAT construiește arborele dominator, arată retain size al fiecărui obiect și oferă analiză automată a scurgerilor suspecte prin Leak Suspects Report.
Xcode Memory Graph — debugger vizual pentru retain cycles. La apăsarea butonului Memory Graph Debugger, Xcode oprește aplicația, construiește graful complet al obiectelor în memorie și evidențiază retain cycles cu roșu.
| Instrument | Platformă | Caracteristică |
|---|---|---|
| LeakCanary | Android | Detectare automată a scurgerilor după destroy |
| Memory Profiler | Android Studio | Heap dump + alocări live |
| Eclipse MAT | Android | Arbore dominator, Leak Suspects Report |
| Memory Graph | iOS (Xcode) | Vizualizator retain cycles |
Conform Google I/O 2023, aplicațiile care folosesc LeakCanary în build-urile de debug reduc numărul de crash-uri legate de memorie cu 30-50% în primele 2 luni după implementare. Se recomandă adăugarea LeakCanary în faza de onboarding a proiectului.
Întrebări frecvente
Scurgerea — obiecte inaccesibile codului, dar neșterse de GC din cauza referințelor active. Umflarea — aplicația reține în memorie obiecte care sunt logic necesare, dar în cantitate excesivă (de exemplu, cache de 50 MB la o aplicație care rulează de 80 MB). Umflarea se tratează arhitectural, scurgerea — prin gestionarea corectă a referințelor.
LeakCanary folosește ObjectWatcher — după onDestroy() Activity creează un WeakReference pe Activity și pornește GC. Dacă după 5 secunde WeakReference nu este curățat, LeakCanary face un heap dump, analizează cel mai scurt lanț de referințe de la GC Root la obiect și arată stiva exactă a scurgerii cu numele fișierului și linia de cod.
Bitmap ocupă memorie în afara heap-ului Java, în memoria nativă (native heap). Dimensiunea unui Bitmap = lățime × înălțime × 4 octeți (ARGB_8888). O fotografie de 12 MP (4000×3000) ocupă 48 MB. Android nu poate întotdeauna elibera la timp memoria nativă, ceea ce la acumularea mai multor Bitmap-uri duce la OOM chiar și cu suficient heap Java.
Retain cycle — o situație în ARC când două obiecte rețin referințe puternice unul la altul, iar contorul de referințe nu ajunge niciodată la zero. Exemplu tipic: ViewController cu o referință puternică la un closure, iar closure capturează self puternic. Soluție: folosiți [weak self] sau [unowned self] în closure-uri.
Dimensiunea heap-ului depinde de dispozitiv și versiunea Android. Pentru dispozitivele vechi (API 15-24) — 64-128 MB. Pentru cele moderne (API 25+) — 256-512 MB. Valoarea exactă poate fi obținută prin ActivityManager.getMemoryClass(). Pentru aplicații mari (jocuri, editoare) există largeHeap=true în manifest, care oferă până la 1 GB.
Rezumat
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.
Citiți și