Что такое утечки памяти? Как найти?
Утечка памяти — это ситуация, когда приложение удерживает ссылки на объекты, которые больше не нужны, из-за чего сборщик мусора (GC) не может освободить соответствующую память. В результате память растёт со временем, появляются OOM-крэши, деградация производительности и повышенное энергопотребление.
Ниже — сжатое, практическое руководство: что бывает, почему, как обнаружить и как лечить (для Android/Java/Kotlin, с общими замечаниями для нативного кода).
1. Типы и причины утечек (коротко)
Долгоживущие ссылки на короткоживущие объекты: статические коллекции, singletons, кеши без лимита.
Контекст (Context) утечки: хранение Activity/View в статике, в длительных объектах → удержание всей Activity.
Неправильные слушатели/коллбэки: не отписались от BroadcastReceiver, Rx, Observer, слушателей View.
Анонимные/внутренние классы: non-static inner class внутри Activity удерживает ссылку на внешнюю Activity.
Неправильная работа с ресурсами: не закрытые Cursors, Streams, FileDescriptors.
Bitmap / large native buffers: большие изображения, native allocations (NDK) — часто вне внимания JVM.
Native leaks: утечки в C/C++ (malloc без free) — память вне Java heap.
Неправильный lifecycle: async-таски (Handler, Runnable, coroutines) удерживают Activity после уничтожения.
2. Признаки утечки
Постоянный рост потребления памяти (heap) при использовании фичи/длительном запуске.
Частые GC, ухудшение FPS, подвисания, OutOfMemoryError.
Нарастающие списки объектов одного типа в heap dump.
3. Стратегия поиска (практический чек-лист)
Воспроизвести: воспроизведите сценарий, где память растёт (например, открыть/закрыть экран 50 раз).
Собрать метрики: использовать Android Profiler → Memory -> record allocation / monitor heap live. Наблюдать рост heap size и native memory.
Вызвать GC вручную (в профайлере или
adb shell am send-trim-memory
не всегда нужен) и посмотреть, уменьшается ли heap. Если нет — возможен leak.Сделать heap dump:
через Android Studio Profiler: Capture Heap Dump.
или через adb:
pid=$(pidof com.example.app) adb shell am dumpheap $pid /sdcard/heap.hprof adb pull /sdcard/heap.hprof
Конвертировать (если нужно)
hprof-conv heap.hprof converted.hprof
.
Анализ heap dump:
Открыть в Android Studio или в Eclipse MAT (Memory Analyzer Tool).
Смотреть Dominator Tree / Retained Set — какие объекты удерживают наибольшую память и кто их удерживает.
Ищите большие количества экземпляров одного класса и цепочки удержания (path to GC root).
Использовать LeakCanary: библиотека, автоматически детектирует утечки экранов/activities/fragments и показывает path to GC root с удобным стек-трейсом. Быстро и эффективно для большинства UI-утечек.
Профилирование native memory: Android Studio Native Memory Profiler,
malloc_info
,simpleperf
, AddressSanitizer (для NDK). LeakCanary 2 умеет помогать и с native в некоторых случаях.Проверить логи/ANR/Crashlytics: иногда OOM stacktrace указывает на причину и место.
4. Инструменты
Android Studio — Memory Profiler (heap dumps, allocation tracking).
LeakCanary — автоматическое выявление утечек на стадии разработки.
Eclipse MAT — глубокий анализ hprof, Dominator Tree.
adb (dumpheap, bugreport), hprof-conv.
Native: Android Studio Native Memory Profiler, simpleperf, ASAN (AddressSanitizer), valgrind (на эмуляторе/спец. сборках).
Firebase Test Lab / CI для регрессионного тестирования памяти.
5. Конкретные паттерны исправления
Отписывать слушатели/Receiver/Callbacks в onPause/onDestroy/в соответствующем lifecycle.
Не хранить Activity/Context в static-полях. Если нужен Context — использовать
applicationContext
для долговременных объектов.Сделать внутренние классы static и использовать
WeakReference
на Activity при необходимости.Очистка коллекций при закрытии компонента; использовать
WeakHashMap
/WeakReference
где уместно.Использовать библиотеки загрузки изображений (Glide/Picasso) — они управляют кешами и ресурсами; явно освобождать большие Bitmap через
recycle()
при ручной работе.Использовать lifecycle-aware компоненты (LiveData, LifecycleOwner) и корутины с
lifecycleScope
/viewLifecycleOwner
.Ограничивать кеши — LRUCache с размером.
Мокать/стабить внешние сети в тестах, чтобы исключить hanging callbacks.
6. Быстрые команды и примеры
Dump heap:
pid=$(pidof com.example.app)
adb shell am dumpheap $pid /sdcard/heap.hprof
adb pull /sdcard/heap.hprof
hprof-conv heap.hprof converted.hprof
# открыть converted.hprof в MAT или Android Studio
LeakCanary добавление (пример Gradle):
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.x"
LeakCanary автоматом покажет уведомление о найденной утечке с анализом.
7. Советы по процессу тестирования памяти
Инструментальный тест-кейc: циклически открывать/закрывать экраны, запускать стресс-тесты (Monkey) и собирать heap dumps через CI для регрессии.
Установить порог роста памяти в CI (например, рост heap > X MB за N итераций — фейлить сборку).
Фокусироваться сначала на больших утечках (быстро приводящих к OOM), затем на хронических «медленных» утечках.
Документировать reproducer-steps и минимальный repro-apk для багов памяти.
8. Короткий чек-лист при нахождении подозрения на утечку
Повторить сценарий и зафиксировать рост memory.
Вызвать GC — если память не падает, делать heap dump.
Проанализировать Dominator Tree → найти GC root → понять, какая цепочка удерживает объект.
Исправить код (отписка, weak refs, static→non-static fix, закрыть ресурсы).
Повторно протестировать.
Это концентрированный план: диагностировать через Profiler → получить heap dump → анализ доминаторов → исправить источник удержания. Для UI-утечек LeakCanary даёт самый быстрый путь, а для нативных — Native Memory Profiler / ASAN / simpleperf.
Last updated
Was this helpful?