OutOfMemoryError — фатални изузетак који настаје када виртуелна машина Java (JVM) или Android Runtime (ART) не може да додијели меморију за нови објекат због недостатка мјеста на гомили (Heap). Према Square Engineering, 70% OutOfMemoryError у мобилним апликацијама је изазвано цурењем меморије, а не стварним прекорачењем лимита. Разумијевање узрока OOM-а је кључ за стабилан рад апликације.
Главно
OutOfMemoryError (OOM) — изузетак из породице VirtualMachineError у Java/Kotlin који сигнализира немогућност додјеле меморије за нови објекат. За разлику од checked-изузетака, OOM је Error и не захтијева обраду кроз catch — иако га технички можете ухватити. Након појаве OOM-а, апликација је обично у нестабилном стању и препоручује се да се затвори.
На Android-у, свака апликација има лимит Heap-а који поставља произвођач уређаја. За модерне паметне телефоне са 6+ GB RAM-а, лимит је 256–512 MB, за буџетске уређаје — 128–192 MB. Када укупна запремина свих живих објеката премаши овај лимит, ART избацује OutOfMemoryError.
Важно је разумјети: OOM не значи увијек да је физичка меморија уређаја потрошена. То значи да је апликација исцрпила свој лимит Heap-а постављен од стране система. Друге апликације могу имати слободне меморије, али ваша апликација је не може користити због изолације процеса на Android-у.
Пет сценарија редовно доводи до OOM-а у мобилним апликацијама. Сваки сценариј је повезан са одређеним типом података или операцијом.
Bitmap — главни потрошач меморије у Android апликацијама. Учитавање FullHD слике (1920 × 1080) у оригиналној величини заузима 8.3 MB у ARGB_8888 формату. Ако у RecyclerView-у има 50 таквих слика — то је 415 MB, што премашује Heap било ког уређаја. Учитавање слика без inSampleSize-а — гарантовани OOM на слабим уређајима.
Користите Glide или Coil за аутоматско скалирање. Ове библиотеке учитавају слике у величини која одговара View-у, а не оригиналној резолуцији. За директну употребу BitmapFactory.Options примијените inSampleSize: израчунајте га као степен двојке тако да коначна величина не прелази 2048 × 2048 пиксела. Додатно користите RGB_565 умјесто ARGB_8888 за слике без провидности — то преполовљује потрошњу меморије.
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)
}
Једно цурење од неколико KB неће изазвати OOM. Али десетине цурења на сваком екрану се акумулирају: сваки прелазак на екран додаје цурење, GC не може да ослободи објекте, Heap се пуни. Типични образац: корисник отвара и затвара екран профила 20 пута → Heap расте за 200 MB → апликација пада са OOM-ом.
Инсталирајте LeakCanary у пројекат за аутоматско откривање цурења. Показаће сваки исцурели објекат са тачним stack trace-ом. Након исправљања свих цурења, потрошња Heap-а постаје стабилна: након затварања екрана, меморија се враћа на основни ниво.
Учитавање датотека у цијелости у byte[] — директан пут ка OOM-у. JSON датотека од 50 MB при парсирању ће креирати стринг исте величине плус DOM модел. Видео датотеке учитане у меморију, аудио бафери и велики protobuf скупови података — сви могу премашити лимит Heap-а у једној операцији.
Обрађујте велике податке токовима: InputStream са бафером 4–8 KB, Streaming JSON-парсер (Jackson или Gson са JsonReader), MediaCodec за видео. Никада не позивајте File.readBytes() на датотекама већим од 10% доступног Heap-а.
Интензивно креирање објеката у петљи без посредног GC-а може довести до OOM-а, посебно на уређајима са малим Heap-ом. Примјер: генерисање 100.000 објеката у for петљи који не стају у Heap прије него што GC успије да их сакупи. Ово је чешће у играма и графичким едиторима.
Користите Object Pool за објекте који се масовно креирају и уништавају. За нумеричке податке користите примитиве (FloatArray умјесто List<Float>). RecyclerView са ViewHolder Pool-ом рјешава овај проблем за UI компоненте.
Фрагментација — стање у којем је слободне меморије укупно довољно, али нема непрекидног блока за нови објекат. ART компактира Heap при GC-у, али не увијек успјешно. Велики низови (Bitmap, byte[]) су најосјетљивији на фрагментацију.
ART на Android 8+ користи Generational GC, који смањује фрагментацију раздвајањем младих и старих објеката. Ипак, избјегавајте додјелу фрагмената различитих величина у истом базену — покушајте користити претходно додијељене бафере фиксне величине.
Лимит Heap-а на Android-у није константа — зависи од произвођача, модела уређаја и верзије ОС-а. Google поставља минималне захтјеве кроз Compatibility Definition Document (CDD), али произвођачи постављају стварне вриједности.
| Категорија уређаја | Типични Heap | largeHeap |
|---|---|---|
| Буџетни (1–2 GB RAM) | 128–192 MB | 256–384 MB |
| Средњи (3–4 GB RAM) | 256–384 MB | 512 MB |
| Врхунски (6+ GB RAM) | 384–512 MB | 768 MB–1 GB |
| Таблети (4+ GB RAM) | 256–512 MB | 768 MB |
| Wear OS | 32–64 MB | Нема |
Можете затражити повећани лимит путем android:largeHeap="true" у манифесту. Користите га опрезно: повећање Heap-а не рјешава проблем цурења и може погоршати корисничко искуство ако систем буде приморан да убија друге апликације ради ослобађања меморије за вашу. За Wear OS лимит Heap-а је минималан — само 32–64 MB, овдје largeHeap није доступан, а уштеда меморије је двоструко критична.
Дијагностика OOM-а захтијева анализу Heap Dump-а и разумијевање који објекти троше меморију. Android Studio пружа све потребне алате.
Корак 1: Ухватите тренутак OOM-а. У Android Memory Profiler-у притисните Record memory allocations и извршите сценариј који изазива пад. Profiler ће показати скок алокација прије OOM-а. Ако се OOM не репродукује, смањите Heap путем android:smallHeap у debug-верзији или користите DDMS са ручним позивом GC-а.
Корак 2: Направите Heap Dump у тренутку вршног оптерећења (прије OOM-а). Отворите Dump у Android Studio-у: картица Classes сортирана по Retained Size. Највећи објекти — Bitmap, byte[], String. За сваки Bitmap погледајте величину (width × height × 4 бајта) и путању учитавања кроз Stack Trace.
Корак 3: Анализирајте број понављајућих објеката. Ако видите 200 идентичних Fragment или Activity — то је цурење. Ако 500 Bitmap исте величине — проблем кеширања слика. MAT (Memory Analyzer Tool) пружа дубљу анализу са Dominator Tree, показујући који објекти држе 80% Heap-а.
// Команда за Heap Dump путем adb
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.
Свеобухватна стратегија превенције OOM-а укључује пет нивоа заштите: од архитектонских рјешења до мониторинга у продукцији.
ViewModel + Repository образац раздваја податке од UI-ја и спречава задржавање View-а при ротацији екрана. ViewModel надживљава Activity, његови подаци се не губе, а View се може поново креирати без дуплирања података у меморији. Користите StateFlow умјесто LiveData за експлицитно управљање стањима.
Glide — обавезна библиотека за рад са сликама. Аутоматски скалира, кешира (диск + меморија) и рециклира Bitmap. Подесите diskCacheStrategy и skipMemoryCache за велике листе. За анимиране слике користите Glide са GIF/WebP — заузимају мање меморије од низа Bitmap-ова.
Firebase Performance Monitoring прати потрошњу меморије у реалном времену. Поставите аларм на заузетост Heap-а преко 80% од лимита — то је сигнал за провјеру. Crashlytics прикупља OOM као изузетак и показује последње познато стање Heap-а прије пада. За Android 11+ користите ApplicationExitInfo за детекцију OOM-завршетака.
Обавезно тестирајте апликацију на уређајима са минималним Heap-ом (128–192 MB). Емулатор са малим екраном и малим Heap-ом емулира буџетни уређај. Ако апликација ради на таквом уређају, на врхунским уређајима неће бити проблема са OOM-ом. Користите Firebase Test Lab са стварним уређајима различитих цијеновних категорија.
// Провјера доступног Heap-а прије тешке операције
fun canAllocate(requiredBytes: Long): Boolean {
val runtime = Runtime.getRuntime()
val free = runtime.freeMemory()
return free > requiredBytes * 2 // резерва 50%
}
Често постављана питања
Технички да, али то се не препоручује. Након OOM-а, апликација се налази у нестабилном стању: нове алокације можда неће радити, а неки објекти могу бити дјелимично креирани. Једина разумна радња у catch-у — логирање и рестартовање Activity-ја.
Лимит Heap-а је различит на различитим уређајима. Операција која захтијева 300 MB пашће на уређају са лимитом 192 MB, али ће проћи на врхунском са 512 MB. Тестирајте на уређајима са минималним карактеристикама за откривање OOM сценарија.
largeHeap повећава лимит, али не убрзава апликацију. GC паузе постају дуже јер сакупљање великог Heap-а траје дуже. Систем може убијати позадинске апликације ради обезбјеђења меморије. Користите largeHeap само за апликације којима објективно треба много меморије (камере, едитори).
OOM — изузетак унутар апликације при недостатку Heap-а. Системско убијање (Low Memory Killer) — одлука Linux кернела да убије процес ради ослобађања меморије за друге апликације. При системском убијању апликација не прима изузетак — процес се једноставно завршава.
Формула: width × height × bytesPerPixel. ARGB_8888 = 4 B/пиксел, RGB_565 = 2 B/пиксел. FullHD Bitmap (1920 × 1080) у ARGB_8888 = 8.3 MB. 4K Bitmap (3840 × 2160) = 33 MB. Увијек скалирајте слике на величину потребну за приказ на екрану.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође