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+ ГБ RAM ліміт становить 256–512 МБ, для бюджетних пристроїв — 128–192 МБ. Коли сумарний обсяг всіх живих об'єктів перевищує цей ліміт, ART викидає OutOfMemoryError.

Важливо розуміти: OOM не завжди означає, що на пристрої закінчилася фізична пам'ять. Це означає, що додаток вичерпав свій ліміт Heap, встановлений системою. Інші додатки можуть мати вільну пам'ять, але ваш додаток не може її використовувати через ізоляцію процесів в Android.

Основні причини OutOfMemoryError

П'ять сценаріїв регулярно призводять до OOM в мобільних додатках. Кожен сценарій пов'язаний з певним типом даних або операцією.

Bitmap без масштабування

Bitmap — головний споживач пам'яті в Android-додатках. Завантаження FullHD-зображення (1920 × 1080) в оригінальному розмірі займає 8,3 МБ у форматі ARGB_8888. Якщо в RecyclerView 50 таких зображень — це 415 МБ, що перевищує 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)
}

Витоки пам'яті (накопичення)

Один витік у кілька КБ не викличе OOM. Але десятки витоків на кожному екрані накопичуються: кожен перехід на екран додає витік, GC не може звільнити об'єкти, Heap заповнюється. Типовий патерн: користувач відкриває та закриває екран профілю 20 разів → Heap зростає на 200 МБ → додаток падає з OOM.

Встановіть LeakCanary в проект для автоматичного виявлення витоків. Він покаже кожен об'єкт, що витік, з точним stack trace. Після виправлення всіх витоків споживання Heap стане стабільним: після закриття екрану пам'ять повертається до базового рівня.

Великі файли в пам'яті

Завантаження файлів цілком у byte[] — прямий шлях до OOM. JSON-файл на 50 МБ при парсингу створить рядок того ж розміру плюс DOM-модель. Відеофайли, завантажені в пам'ять, аудіо-буфери та великі protobuf-датасети — всі вони можуть перевищити ліміт Heap за одну операцію.

Обробляйте великі дані потоками: InputStream з буфером 4–8 КБ, Streaming JSON-парсер (Jackson або Gson з JsonReader), MediaCodec для відео. Ніколи не викликайте File.readBytes() на файлах розміром понад 10% від доступного Heap.

Створення багатьох об'єктів у циклі

Інтенсивне створення об'єктів у циклі без проміжного GC може призвести до OOM, особливо на пристроях з малим Heap. Приклад: генерація 100 000 об'єктів у for-loop, які не вміщаються в 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 ГБ RAM)128–192 МБ256–384 МБ
Середні (3–4 ГБ RAM)256–384 МБ512 МБ
Флагмани (6+ ГБ RAM)384–512 МБ768 МБ–1 ГБ
Планшети (4+ ГБ RAM)256–512 МБ768 МБ
Wear OS32–64 МБНемає

Запросити збільшений ліміт можна через android:largeHeap="true" в маніфесті. Використовуйте його обережно: збільшення Heap не вирішує проблему витоків і може погіршити користувацький досвід, якщо система буде змушена вбивати інші додатки для звільнення пам'яті для вашого. Для Wear OS ліміт Heap мінімальний — лише 32–64 МБ, тут 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 подивіться розмір (ширина × висота × 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 — обов'язкова бібліотека для роботи із зображеннями. Вона автоматично масштабує, кешує (диск + пам'ять) та recycle Bitmap. Налаштуйте diskCacheStrategy та skipMemoryCache для великих списків. Для анімованих зображень використовуйте Glide з GIF/WebP — вони займають менше пам'яті, ніж послідовність Bitmap.

Моніторинг у продакшені

Firebase Performance Monitoring відстежує споживання пам'яті в реальному часі. Встановіть алерт на зайняття Heap понад 80% від ліміту — це сигнал для перевірки. Crashlytics збирає OOM як виняток та показує last known Heap state перед падінням. Для Android 11+ використовуйте ApplicationExitInfo для детекції OOM-завершень.

Тестування на слабких пристроях

Обов'язково тестуйте додаток на пристроях з мінімальним Heap (128–192 МБ). Емулятор з маленьким екраном та малим 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 — логування та restart Activity.

Чому OOM відбувається не на всіх пристроях?

Ліміт Heap різний на різних пристроях. Операція, яка вимагає 300 МБ, впаде на пристрої з лімітом 192 МБ, але пройде на флагмані з 512 МБ. Тестуйте на пристроях з мінімальними характеристиками для виявлення OOM-сценаріїв.

Як largeHeap впливає на продуктивність?

largeHeap збільшує ліміт, але не прискорює додаток. GC-паузи стають довшими, оскільки збирати великий Heap довше. Система може вбивати фонові додатки для забезпечення пам'яті. Використовуйте largeHeap лише для додатків, яким об'єктивно потрібно багато пам'яті (камери, редактори).

Чим OOM відрізняється від системного вбивства процесу?

OOM — виняток всередині додатка при нестачі Heap. Системне вбивство (Low Memory Killer) — рішення Linux-ядра вбити процес, щоб звільнити пам'ять для інших додатків. При системному вбивстві додаток не отримує виняток — процес просто завершується.

Скільки пам'яті реально займає Bitmap?

Формула: ширина × висота × bytesPerPixel. ARGB_8888 = 4 Б/піксель, RGB_565 = 2 Б/піксель. FullHD Bitmap (1920 × 1080) в ARGB_8888 = 8,3 МБ. 4K Bitmap (3840 × 2160) = 33 МБ. Завжди масштабуйте зображення до розміру, необхідного для відображення на екрані.

Підсумки

  • OutOfMemoryError — фатальний виняток при вичерпанні Heap-ліміту додатка
  • Bitmap без масштабування — головний винуватець OOM в мобільних додатках
  • Витоки пам'яті викликають 70% OOM через накопичення об'єктів від кожного переходу
  • Ліміт Heap варіюється від 128 МБ на бюджетних до 512 МБ на флагманських пристроях
  • Heap Dump з аналізом Retained Size — основний інструмент діагностики OOM
  • Glide або Coil обов'язкові для роботи із зображеннями будь-якого розміру
  • Тестування на пристроях з мінімальним Heap — must-have для всіх проектів

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також