OutOfMemoryError — o excepție fatală care apare atunci când mașina virtuală Java (JVM) sau Android Runtime (ART) nu poate aloca memorie pentru un obiect nou din cauza lipsei de spațiu în Heap. Potrivit Square Engineering, 70% din OutOfMemoryError-ürile în aplicațiile mobile sunt cauzate de scurgeri de memorie, nu de depășirea reală a limitei. Înțelegerea cauzelor OOM este cheia funcționării stabile a aplicației.
Principalele puncte
OutOfMemoryError (OOM) — este o excepție din familia VirtualMachineError în Java/Kotlin care semnalează imposibilitatea de a aloca memorie pentru un obiect nou. Spre deosebire de excepțiile checked, OOM este o eroare (Error) și nu necesită gestionare prin catch — deși tehnic poate fi prinsă. După apariția OOM, aplicația se află de obicei într-o stare instabilă și se recomandă să fie închisă.
Pe Android, fiecare aplicație are o limită Heap stabilită de producătorul dispozitivului. Pentru smartphone-urile moderne cu 6+ GB RAM, limita este de 256–512 MB, pentru dispozitivele bugetare — 128–192 MB. Când volumul total al tuturor obiectelor active depășește această limită, ART aruncă OutOfMemoryError.
Important de înțeles: OOM nu înseamnă întotdeauna că memoria fizică a dispozitivului s-a terminat. Înseamnă că aplicația și-a epuizat limita Heap stabilită de sistem. Alte aplicații pot avea memorie liberă, dar aplicația ta nu o poate folosi din cauza izolării proceselor în Android.
Cinci scenarii duc în mod regulat la OOM în aplicațiile mobile. Fiecare scenariu este legat de un anumit tip de date sau operație.
Bitmap — principalul consumator de memorie în aplicațiile Android. Încărcarea unei imagini FullHD (1920 × 1080) în dimensiunea originală ocupă 8.3 MB în format ARGB_8888. Dacă în RecyclerView sunt 50 de astfel de imagini — aceasta înseamnă 415 MB, ceea ce depășește Heap-ul oricărui dispozitiv. Încărcarea imaginilor fără inSampleSize — OOM garantat pe dispozitivele slabe.
Folosește Glide sau Coil pentru scalare automată. Aceste biblioteci încarcă imagini la o dimensiune corespunzătoare View-ului, nu rezoluției originale. Pentru utilizarea directă a BitmapFactory.Options, aplică inSampleSize: calculează-l ca putere a lui doi, astfel încât dimensiunea finală să nu depășească 2048 × 2048 pixeli. Suplimentar, folosește RGB_565 în loc de ARGB_8888 pentru imagini fără transparență — aceasta reduce consumul de memorie la jumătate.
fun loadScaledBitmap(path: String, reqWidth: Int): Bitmap? {
val opts = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(path, opts)
opts.inSampleSize = calculateSampleSize(opts.outWidth, reqWidth)
opts.inJustDecodeBounds = false
return BitmapFactory.decodeFile(path, opts)
}
O scurgere de câțiva KB nu va cauza OOM. Dar zeci de scurgeri pe fiecare ecran se acumulează: fiecare tranziție la un ecran adaugă o scurgere, GC nu poate elibera obiectele, Heap se umple. Modelul tipic: utilizatorul deschide și închide ecranul de profil de 20 de ori → Heap crește cu 200 MB → aplicația crapă cu OOM.
Instalează LeakCanary în proiect pentru detectarea automată a scurgerilor. Acesta va arăta fiecare obiect scurs cu un stack trace exact. După remedierea tuturor scurgerilor, consumul Heap devine stabil: după închiderea ecranului, memoria revine la nivelul de bază.
Încărcarea fișierelor în totalitate în byte[] — calea directă către OOM. Un fișier JSON de 50 MB la parsare va crea un șir de aceeași dimensiune plus modelul DOM. Fișierele video încărcate în memorie, bufferele audio și seturile mari de date protobuf — toate pot depăși limita Heap într-o singură operație.
Procesează datele mari cu fluxuri: InputStream cu buffer de 4–8 KB, parser JSON streaming (Jackson sau Gson cu JsonReader), MediaCodec pentru video. Nu apela niciodată File.readBytes() pe fișiere mai mari de 10% din Heap-ul disponibil.
Crearea intensivă de obiecte în buclă fără GC intermediar poate duce la OOM, în special pe dispozitive cu Heap mic. Exemplu: generarea a 100.000 de obiecte într-o buclă for care nu încap în Heap până când GC apucă să le colecteze. Aceasta este mai frecventă în jocuri și editoare grafice.
Folosește Object Pool pentru obiectele care sunt create și distruse în masă. Pentru date numerice, folosește tipuri primitive (FloatArray în loc de List<Float>). RecyclerView cu ViewHolder Pool rezolvă această problemă pentru componentele UI.
Fragmentarea — starea în care memoria liberă este suficientă în total, dar nu există un bloc continuu pentru un obiect nou. ART compactează Heap-ul în timpul GC, dar nu întotdeauna cu succes. Tablourile mari (Bitmap, byte[]) sunt cele mai sensibile la fragmentare.
ART pe Android 8+ folosește Generational GC, care reduce fragmentarea prin separarea obiectelor tinere și vechi. Cu toate acestea, evită alocarea de fragmente de dimensiuni diferite în aceeași zonă — încearcă să folosești buffere pre-alocate de dimensiune fixă.
Limita Heap în Android nu este o constantă — depinde de producător, modelul dispozitivului și versiunea OS. Google stabilește cerințele minime prin Compatibility Definition Document (CDD), dar producătorii stabilesc valorile reale.
| Categoria dispozitivelor | Heap tipic | largeHeap |
|---|---|---|
| Bugetare (1–2 GB RAM) | 128–192 MB | 256–384 MB |
| Medii (3–4 GB RAM) | 256–384 MB | 512 MB |
| Flagship (6+ GB RAM) | 384–512 MB | 768 MB–1 GB |
| Tablete (4+ GB RAM) | 256–512 MB | 768 MB |
| Wear OS | 32–64 MB | Nu |
Poți solicita o limită mărită prin android:largeHeap="true" în manifest. Folosește-l cu prudență: creșterea Heap nu rezolvă problema scurgerilor și poate înrăutăți experiența utilizatorului dacă sistemul este forțat să ucidă alte aplicații pentru a elibera memorie pentru a ta. Pentru Wear OS, limita Heap este minimă — doar 32–64 MB, aici largeHeap nu este disponibil, iar economisirea memoriei este dublu de critică.
Diagnosticarea OOM necesită analiza Heap Dump și înțelegerea care obiecte consumă memorie. Android Studio oferă toate instrumentele necesare.
Pasul 1: Prinde momentul OOM. În Android Memory Profiler, apasă Record memory allocations și execută scenariul care cauzează crash-ul. Profilerul va arăta saltul de alocări înainte de OOM. Dacă OOM nu se reproduce, reduce Heap-ul prin android:smallHeap în build-ul de debug sau folosește DDMS cu apel manual GC.
Pasul 2: Fă un Heap Dump în momentul de sarcină maximă (înainte de OOM). Deschide Dump-în Android Studio: fila Classes sortată după Retained Size. Cele mai mari obiecte — Bitmap, byte[], String. Pentru fiecare Bitmap, verifică dimensiunea (width × height × 4 octeți) și calea de încărcare prin Stack Trace.
Pasul 3: Analizează numărul de obiecte repetate. Dacă vezi 200 de Fragment sau Activity identice — este o scurgere. Dacă 500 de Bitmap cu aceeași dimensiune — problemă de cache a imaginilor. MAT (Memory Analyzer Tool) oferă o analiză mai profundă cu Dominator Tree, arătând care obiecte rețin 80% din Heap.
// Comanda pentru Heap Dump prin adb
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.
O strategie complexă de prevenire a OOM include cinci niveluri de protecție: de la soluții arhitecturale până la monitorizare în producție.
ViewModel + Repository — modelul separă datele de UI și previne reținerea View-ului la rotirea ecranului. ViewModel supraviețuiește Activity, datele sale nu se pierd, iar View poate fi recreat fără duplicarea datelor în memorie. Folosește StateFlow în loc de LiveData pentru gestionarea explicită a stărilor.
Glide — biblioteca obligatorie pentru lucrul cu imagini. Aceasta scalează automat, face cache (disc + memorie) și reciclează Bitmap. Configurează diskCacheStrategy și skipMemoryCache pentru liste mari. Pentru imagini animate, folosește Glide cu GIF/WebP — ocupă mai puțină memorie decât o secvență de Bitmap.
Firebase Performance Monitoring urmărește consumul de memorie în timp real. Setează o alertă la ocuparea Heap de peste 80% din limită — acesta este un semnal de verificare. Crashlytics colectează OOM ca excepție și arată ultima stare cunoscută a Heap-înainte de crash. Pentru Android 11+, folosește ApplicationExitInfo pentru detectarea terminărilor OOM.
Testează obligatoriu aplicația pe dispozitive cu Heap minim (128–192 MB). Emulatorul cu ecran mic și Heap mic simulează un dispozitiv bugetar. Dacă aplicația funcționează pe un astfel de dispozitiv, pe flagship-uri nu vor fi probleme cu OOM. Folosește Firebase Test Lab cu dispozitive reale din diferite categorii de preț.
// Verificarea Heap-ului disponibil înainte de o operație grea
fun canAllocate(requiredBytes: Long): Boolean {
val runtime = Runtime.getRuntime()
val free = runtime.freeMemory()
return free > requiredBytes * 2 // rezervă 50%
}
Întrebări frecvente
Tehnic da, dar nu este recomandat. După OOM, aplicația se află într-o stare instabilă: noile alocări pot să nu funcționeze, iar unele obiecte pot fi parțial create. Singura acțiune rezonabilă în catch este logarea și restartarea Activity.
Limita Heap este diferită pe diferite dispozitive. O operație care necesită 300 MB va cădea pe un dispozitiv cu limită de 192 MB, dar va funcționa pe un flagship cu 512 MB. Testează pe dispozitive cu specificații minime pentru a descoperi scenariile OOM.
largeHeap crește limita, dar nu accelerează aplicația. Pauzele GC devin mai lungi, deoarece colectarea unui Heap mare durează mai mult. Sistemul poate ucide aplicații în fundal pentru a asigura memoria. Folosește largeHeap doar pentru aplicații care obiectiv au nevoie de multă memorie (camere, editoare).
OOM — excepție în interiorul aplicației la lipsa Heap. Uciderea sistemului (Low Memory Killer) — decizia nucleului Linux de a ucide un proces pentru a elibera memorie pentru alte aplicații. La uciderea sistemului, aplicația nu primește o excepție — procesul pur și simplu se termină.
Formula: width × height × bytesPerPixel. ARGB_8888 = 4 B/pixel, RGB_565 = 2 B/pixel. Bitmap FullHD (1920 × 1080) în ARGB_8888 = 8.3 MB. Bitmap 4K (3840 × 2160) = 33 MB. Scalează întotdeauna imaginile la dimensiunea necesară pentru afișare pe ecran.
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