← Назад к вопросам

Как решишь OutOfMemoryError

1.8 Middle🔥 191 комментариев
#JVM и память#Производительность и оптимизация

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Общая стратегия решения OutOfMemoryError (OOM)

OutOfMemoryError — критическая ошибка, указывающая на исчерпание доступной heap-памяти в виртуальной машине Dalvik/ART. Решение требует системного подхода: диагностика, идентификация утечек, оптимизация использования памяти и мониторинг.

1. Диагностика и анализ

Первым шагом является определение типа утечки и объектов, удерживающих память.

Использование профилировщиков:

  • Android Studio Profiler (Memory) — основной инструмент. Записывайте Heap Dump, анализируйте распределение объектов по классам, ищите "подозрительные" активности, фрагменты, Bitmap'ы, большие массивы.
  • LeakCanary — автоматическая детекция утечек. Библиотека показывает цепочки ссылок, не позволяющие объектам быть собранными GC.

Пример анализа в коде (упрощённо):

// Включение строгого режима для детекции утечек активности
if (BuildConfig.DEBUG) {
    StrictMode.setVmPolicy(
        StrictMode.VmPolicy.Builder()
            .detectActivityLeaks()
            .penaltyLog()
            .build()
    )
}

2. Основные причины и решения

A. Утечки контекста и жизненного цикла

Наиболее частая причина — неправильные ссылки на Context, Activity, View или Fragment.

Типичные сценарии:

  • Синглтоны, хранящие контекст активности вместо контекста приложения.
  • Коллбеки, слушатели (Listeners), не отписывающиеся при уничтожении объекта.
  • Статические поля, ссылающиеся на view или контекст активности.

Решение:

// НЕПРАВИЛЬНО: статическое поле, удерживающее активность
companion object {
    private var sActivity: MainActivity? = null // Утечка!
}

// ПРАВИЛЬНО: используйте WeakReference или контекст приложения
companion object {
    private var sActivityRef: WeakReference<MainActivity>? = null
}

// Для синглтона используйте Application Context
class MySingleton private constructor(context: Context) {
    init {
        val appContext = context.applicationContext
        // ...
    }
}

B. Неоптимизированные Bitmap'ы

Bitmap — главный потребитель памяти. Ошибки: загрузка полноразмерных изображений без сэмплинга.

Решение:

// Используйте BitmapFactory.Options с inSampleSize
fun decodeSampledBitmapFromResource(res: Resources, resId: Int, reqWidth: Int, reqHeight: Int): Bitmap {
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    BitmapFactory.decodeResource(res, resId, options)

    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
    options.inJustDecodeBounds = false
    return BitmapFactory.decodeResource(res, resId, options)
}

private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    val (height, width) = options.run { outHeight to outWidth }
    var inSampleSize = 1
    if (height > reqHeight || width > reqWidth) {
        val halfHeight = height / 2
        val halfWidth = width / 2
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }
    return inSampleSize
}
  • Всегда вызывайте bitmap.recycle() для Bitmap, созданных вручную (не через BitmapFactory).
  • Используйте библиотеки (Glide, Picasso, Coil), которые автоматически управляют памятью и кэшированием.

C. Большие коллекции и кэши

  • Очищайте коллекции (списки, массивы, кэши) при ненадобности.
  • Используйте SoftReference или WeakReference для кэшей, если уместно.
  • Реализуйте LRU-кэш (LruCache) для контроля размера.

D. Ресурсоёмкие операции в цикле или асинхронных задачах

Создание множества объектов в циклах или незавершённые AsyncTask'и.

Решение:

  • Используйте пулы объектов (Object Pooling) для часто создаваемых тяжёлых объектов.
  • Отменяйте AsyncTask, Coroutine, RxJava Subscription при уничтожении компонента.

3. Технические приёмы и настройки

Увеличение heap-памяти (временная мера):

В AndroidManifest.xml для конкретной активности:

<application
    android:largeHeap="true">

Важно: Это маскирует проблему, а не решает её. Используйте только для memory-intensive операций (например, обработка больших изображений).

Оптимизация сборки мусора (GC):

  • Избегайте создания объектов в критичных по производительности участках (onDraw, в циклах).
  • Используйте примитивные типы вместо обёрток, SparseArray вместо HashMap<Integer, Object>.

4. Профилактика и мониторинг

  • Регулярное тестирование на устройствах с малым объемом памяти.
  • Мониторинг в продакшене: отправка отчетов об OOM через Firebase Crashlytics или аналоги.
  • Code Review с фокусом на жизненный цикл и управление памятью.

Пример чек-листа при возникновении OOM:

  1. Снял Heap Dump в Android Profiler?
  2. Проверил LeakCanary на наличие утечек?
  3. Проанализировал Bitmap'ы — размер, сэмплинг, ресайзинг?
  4. Отписался от всех listeners/observers в onDestroy/onCleared?
  5. Очистил кэши и большие коллекции при onLowMemory()?
  6. Использовал WeakReference/SoftReference для глобальных ссылок?

Итог: Решение OOM — это не разовая операция, а культура разработки. Постоянный мониторинг, использование современных библиотек (Coroutines + Lifecycle, Jetpack), понимание жизненного цикла компонентов и профилирование — ключевые практики для стабильного приложения.

Как решишь OutOfMemoryError | PrepBro