Жрёт память и пухнет — что это, причины и как избежать

Автор: IT Sectr Опубликовано: 2026-07-28 Время чтения: 10 мин

Утечка памяти — одна из самых коварных проблем в мобильной разработке. Память приложения неуклонно растёт, пока не достигает предела, установленного ОС, после чего следует OutOfMemoryError или принудительное завершение. По данным Square Engineering, около 40% Android-приложений имеют хотя бы одну утечку памяти, которая может быть обнаружена только при профилировании. Разберём причины и методы предотвращения роста памяти.

Главное

  • GC reachability — объект не удаляется, если на него есть активная ссылка из корневого набора
  • Статические ссылки на Activity или Context — самая частая причина утечки в Android
  • LeakCanary — стандартный инструмент для автоматического обнаружения утечек в Android
  • WeakReference — решение для ссылок, которые не должны препятствовать сборке мусора
  • Lifecycle-aware компоненты автоматически отменяют подписки при уничтожении вью

Что такое утечка памяти и раздувание приложения?

Утечка памяти (memory leak) — ситуация, когда объект, который больше не нужен приложению, продолжает удерживаться в heap, потому что на него остаётся активная ссылка от корневого набора (GC Root). Сборщик мусора считает такой объект живым и не удаляет его.

Раздувание памяти (memory bloat) — более широкая проблема, когда приложение потребляет больше памяти, чем необходимо для выполнения текущих задач. Причины: избыточное кэширование, дублирование объектов, неоптимальные структуры данных и фрагментация heap.

В Android на каждое приложение выделяется ограниченный heap (обычно 64-512 MB в зависимости от устройства и версии ОС). В iOS ограничение менее жёсткое, но система отправляет memory warning при приближении к лимиту.

ХарактеристикаAndroidiOS
Ограничение heap64-512 MB (зависит от устройства)Неявное (системное)
Сборка мусораART (Concurrent, Compact)ARC (Automatic Reference Counting)
Механизм утечкиGC Root referencesRetain cycles (strong reference cycles)
ИтогOutOfMemoryErrorMemory warning → termination

По данным Facebook Engineering Blog, утечки памяти — причина ~15% crash-репортов в мобильных приложениях. В Android к этому добавляются ANR из-за частых GC пауз при нехватке памяти.

Типичные паттерны утечек памяти в Android и iOS

Статическая ссылка на Activity — классика Android-утечек. Если статическое поле или синглтон хранит ссылку на Activity, она не будет собрана GC даже после finish(), пока синглтон жив. Activity — тяжёлый объект, содержащий View hierarchy, resources и Context.

kotlin
object LeakHolder {
    var activityRef: Activity ?= null // leak: static reference to Activity
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle ?= null) {
        super.onCreate(savedInstanceState)
        LeakHolder.activityRef = this // ❌ MainActivity will never be GC'd
    }
}

Анонимные классы и лямбды — неявно удерживают ссылку на внешний класс. Если Runnable или Callback передаётся во внешний сервис, а Activity уничтожается, объект анонимного класса всё ещё висит в очереди и не даёт Activity уйти на сборку мусора.

  • Handler с задержкой — если Activity уничтожена, а Handler.postDelayed ещё не выполнен, Activity утекает
  • Thread и AsyncTask — при повороте экрана Activity пересоздаётся, а старый Thread продолжает держать ссылку на старую Activity
  • Retrofit/Callback — анонимный Callback удерживает ссылку на презентер или фрагмент
  • Наблюдатели (Observers) — LiveData или RxJava подписки без отписки при onDestroy

В iOS основная проблема — retain cycles: два объекта держат сильные ссылки друг на друга, и ARC не может обнулить счётчик ссылок ни для одного из них. Типичный случай: closure, захватывающий self сильно, и self, держащий ссылку на closure.

Как обнаружить утечки памяти?

LeakCanary — библиотека от Square для автоматического обнаружения утечек в Android. После уничтожения Activity или Fragment она проверяет, что объект был собран GC. Если нет — делает heap dump и показывает trace утечки.

kotlin
// LeakCanary 2.x — auto-integration via Application
class ExampleApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // LeakCanary auto-installs in debug build
        // via ContentProvider — zero code setup
    }
}

// Force check invocation
AppWatcher.objectWatcher.watch(watchedObject, "leak description")

Android Studio Profiler — встроенный инструмент для мониторинга памяти в реальном времени. Позволяет записать heap dump, найти объекты-подозреватые (Retained Size > 1 MB), и проследить GC root path до каждого объекта.

Для iOS используйте Xcode Memory Graph Debugger. Он визуализирует граф объектов в памяти, показывает retain cycles и позволяет мгновенно обнаружить круговые ссылки. Также доступен Instruments > Allocations для долгосрочного мониторинга.

Стратегии предотвращения утечек

WeakReference — базовый механизм для ссылок, которые не должны мешать сборке мусора. Если GC решит удалить объект, WeakReference вернёт null. Используется для обратных вызовов, слушателей и ссылок на UI-компоненты из фоновых потоков.

Lifecycle-aware компоненты — архитектурный подход, реализованный в Android Jetpack (Lifecycle, LiveData, Flow, coroutines). Подписки автоматически отменяются при onDestroy, что исключает основной класс утечек.

kotlin
class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<List<User>>()
    val data: LiveData<List<User>> get() = _data

    fun loadData() {
        viewModelScope.launch {
            val result = repository.fetchData()
            _data.postValue(result)
            // coroutine auto-cancels on onCleared()
        }
    }
}

viewModelScope и lifecycleScope — встроенные CoroutineScope в Android, которые отменяются при соответствующем событии жизненного цикла. Это исключает утечки через корутины — наиболее частый сценарий в современной Android-разработке.

  • Не используйте статические ссылки на Context, Activity, View или Fragment
  • Отменяйте все RxJava подписки в disposeBag / CompositeDisposable при onDestroy
  • Используйте [weak self] / [unowned self] в iOS-замыканиях для предотвращения retain cycles
  • Проверяйте Bitmap и большие объекты — они должны быть recycled или nullified

Инструменты для профилирования памяти

Memory Profiler in Android Studio — основной инструмент для мониторинга heap. Показывает live allocations, снимки heap, количество объектов по типам. Позволяет записать дамп и проанализировать его в MAT (Memory Analyzer Tool) для поиска подозрительных объектов.

Eclipse MAT — десктопный анализатор heap dump. После загрузки HPROF-файла из Android Studio, MAT строит дерево dominator, показывает retain size каждого объекта и предлагает автоматический анализ подозрительных утечек через Leak Suspects Report.

Xcode Memory Graph — визуальный дебаггер retain cycles. При нажатии на кнопку Memory Graph Debugger Xcode останавливает приложение, строит полный граф объектов в памяти и подсвечивает retain cycles красным цветом.

ИнструментПлатформаОсобенность
LeakCanaryAndroidАвтообнаружение утечек после destroy
Memory ProfilerAndroid StudioHeap dump + live allocations
Eclipse MATAndroidDominator tree, Leak Suspects Report
Memory GraphiOS (Xcode)Retain cycles visualiser

По данным Google I/O 2023, приложения, использующие LeakCanary в debug-сборках, сокращают количество memory-related crash на 30-50% за первые 2 месяца после внедрения. Рекомендуется добавлять LeakCanary на этапе onboarding проекта.

Часто задаваемые вопросы

Чем отличается утечка памяти от раздувания?

Утечка — объекты, не доступные коду, но не удаляемые GC из-за активных ссылок. Раздувание — приложение держит в памяти объекты, которые логически нужны, но в избыточном количестве (например, кеш на 50 MB при работающем приложении весом 80 MB). Раздувание лечится архитектурно, утечка — через корректное управление ссылками.

Как LeakCanary находит утечки?

LeakCanary использует ObjectWatcher — после onDestroy() Activity он создаёт WeakReference на Activity и запускает GC. Если через 5 секунд WeakReference не очищен, LeakCanary делает heap dump, анализирует shortest reference chain от GC Root до объекта и показывает точный стек утечки с указанием файла и строки кода.

Почему Bitmap часто вызывает OutOfMemoryError?

Bitmap занимает память вне heap Java в нативной памяти (native heap). Размер одного Bitmap = ширина × высота × 4 байта (ARGB_8888). Фото 12 MP (4000×3000) занимает 48 MB. Android не всегда может своевременно освободить native memory, что при накоплении нескольких Bitmap приводит к OOM даже при достаточном Java heap.

Что такое retain cycle в iOS?

Retain cycle — ситуация в ARC, когда два объекта держат сильные ссылки друг на друга, и счётчик ссылок никогда не достигает нуля. Типичный пример: ViewController с сильной ссылкой на closure, а closure захватывает self сильно. Решение: использовать [weak self] или [unowned self] в замыканиях.

Какой максимальный размер heap на Android?

Размер heap зависит от устройства и версии Android. Для старых устройств (API 15-24) — 64-128 MB. Для современных (API 25+) — 256-512 MB. Точное значение можно получить через ActivityManager.getMemoryClass(). Для больших приложений (игры, редакторы) есть largeHeap=true в манифесте, дающее до 1 GB.

Итоги

  • Утечка памяти — объект, не удаляемый GC из-за активной ссылки от корневого набора; раздувание — избыточное потребление памяти без явных утечек
  • Статические ссылки на Activity, Context, View — номер один среди причин утечек в Android; решение — WeakReference или Application Context
  • Анонимные классы и лямбды неявно держат ссылку на внешний класс; неотменённые коллбэки — вторая по частоте причина
  • LeakCanary — стандарт автообнаружения утечек в Android; интеграция занимает 5 минут и сокращает crash rate на 30-50%
  • lifecycleScope и viewModelScope автоматически отменяют корутины при destroy, исключая целый класс утечек
  • Retain cycles в iOS решаются через weak/unowned self в замыканиях и делегатах
  • Профилируйте память не реже одного раза в спринт — heap dump с MAT или Memory Graph должен стать частью code review

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

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

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