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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също