OutOfMemoryError у развоју апликација: шта је, узроци и методе превенције

Аутор: IT Sectr Објављено: 2026-03-29 Време читања: 9 мин

OutOfMemoryError — фатални изузетак који настаје када виртуелна машина Java (JVM) или Android Runtime (ART) не може да додијели меморију за нови објекат због недостатка мјеста на гомили (Heap). Према Square Engineering, 70% OutOfMemoryError у мобилним апликацијама је изазвано цурењем меморије, а не стварним прекорачењем лимита. Разумијевање узрока OOM-а је кључ за стабилан рад апликације.

Главно

  • OutOfMemoryError — изузетак при недостатку Heap-а за креирање новог објекта
  • Heap (гомила) — област меморије гдје живе сви Java/Kotlin објекти
  • Bitmap — главни потрошач Heap-а на Android-у, типични извор OOM-а
  • Heap Dump — снимак гомиле за анализу ко и колико меморије заузима
  • Лијечење OOM-а захтијева уклањање цурења и оптимизацију потрошње меморије

Шта је OutOfMemoryError

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-у.

Главни узроци OutOfMemoryError

Пет сценарија редовно доводи до OOM-а у мобилним апликацијама. Сваки сценариј је повезан са одређеним типом података или операцијом.

Bitmap без скалирања

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 за слике без провидности — то преполовљује потрошњу меморије.

kotlin
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 компоненте.

Фрагментација Heap-а

Фрагментација — стање у којем је слободне меморије укупно довољно, али нема непрекидног блока за нови објекат. ART компактира Heap при GC-у, али не увијек успјешно. Велики низови (Bitmap, byte[]) су најосјетљивији на фрагментацију.

ART на Android 8+ користи Generational GC, који смањује фрагментацију раздвајањем младих и старих објеката. Ипак, избјегавајте додјелу фрагмената различитих величина у истом базену — покушајте користити претходно додијељене бафере фиксне величине.

Лимити Heap-а на Android-у

Лимит Heap-а на Android-у није константа — зависи од произвођача, модела уређаја и верзије ОС-а. Google поставља минималне захтјеве кроз Compatibility Definition Document (CDD), али произвођачи постављају стварне вриједности.

Категорија уређајаТипични HeaplargeHeap
Буџетни (1–2 GB RAM)128–192 MB256–384 MB
Средњи (3–4 GB RAM)256–384 MB512 MB
Врхунски (6+ GB RAM)384–512 MB768 MB–1 GB
Таблети (4+ GB RAM)256–512 MB768 MB
Wear OS32–64 MBНема

Можете затражити повећани лимит путем android:largeHeap="true" у манифесту. Користите га опрезно: повећање Heap-а не рјешава проблем цурења и може погоршати корисничко искуство ако систем буде приморан да убија друге апликације ради ослобађања меморије за вашу. За Wear OS лимит Heap-а је минималан — само 32–64 MB, овдје largeHeap није доступан, а уштеда меморије је двоструко критична.

Дијагностика OutOfMemoryError

Дијагностика 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-а.

text
// Команда за Heap Dump путем adb
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.

Стратегије превенције OOM-а

Свеобухватна стратегија превенције OOM-а укључује пет нивоа заштите: од архитектонских рјешења до мониторинга у продукцији.

Архитектонска рјешења

ViewModel + Repository образац раздваја податке од UI-ја и спречава задржавање View-а при ротацији екрана. ViewModel надживљава Activity, његови подаци се не губе, а View се може поново креирати без дуплирања података у меморији. Користите StateFlow умјесто LiveData за експлицитно управљање стањима.

Управљање Bitmap-ом и сликама

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 са стварним уређајима различитих цијеновних категорија.

kotlin
// Провјера доступног Heap-а прије тешке операције
fun canAllocate(requiredBytes: Long): Boolean {
    val runtime = Runtime.getRuntime()
    val free = runtime.freeMemory()
    return free > requiredBytes * 2 // резерва 50%
}

Често постављана питања

Може ли се OutOfMemoryError ухватити путем try-catch?

Технички да, али то се не препоручује. Након OOM-а, апликација се налази у нестабилном стању: нове алокације можда неће радити, а неки објекти могу бити дјелимично креирани. Једина разумна радња у catch-у — логирање и рестартовање Activity-ја.

Зашто се OOM не дешава на свим уређајима?

Лимит Heap-а је различит на различитим уређајима. Операција која захтијева 300 MB пашће на уређају са лимитом 192 MB, али ће проћи на врхунском са 512 MB. Тестирајте на уређајима са минималним карактеристикама за откривање OOM сценарија.

Како largeHeap утиче на перформансе?

largeHeap повећава лимит, али не убрзава апликацију. GC паузе постају дуже јер сакупљање великог Heap-а траје дуже. Систем може убијати позадинске апликације ради обезбјеђења меморије. Користите largeHeap само за апликације којима објективно треба много меморије (камере, едитори).

По чему се OOM разликује од системског убијања процеса?

OOM — изузетак унутар апликације при недостатку Heap-а. Системско убијање (Low Memory Killer) — одлука Linux кернела да убије процес ради ослобађања меморије за друге апликације. При системском убијању апликација не прима изузетак — процес се једноставно завршава.

Колико меморије стварно заузима Bitmap?

Формула: 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. Увијек скалирајте слике на величину потребну за приказ на екрану.

Резиме

  • OutOfMemoryError — фатални изузетак при исцрпљењу Heap лимита апликације
  • Bitmap без скалирања — главни кривац за OOM у мобилним апликацијама
  • Цурење меморије узрокује 70% OOM-а кроз акумулацију објеката при свакој навигацији
  • Лимит Heap-а варира од 128 MB на буџетним до 512 MB на врхунским уређајима
  • Heap Dump са анализом Retained Size — главни алат за дијагностику OOM-а
  • Glide или Coil су обавезни за рад са сликама било које величине
  • Тестирање на уређајима са минималним Heap-ом — must-have за све пројекте

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође