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-модель. Видеофайлы, загруженные в память, audio-буферы и большие 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, который снижает фрагментацию за счёт разделения молодых и старых объектов. Тем не менее, избегайте выделения фрагментов разного размера в одном пуле — старайтесь использовать pre-allocated буферы фиксированного размера.

Лимиты 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 посмотрите размер (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 — обязательная библиотека для работы с изображениями. Она автоматически масштабирует, кэширует (диск + память) и 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?

Формула: width × height × 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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