Как LeakCanary находит утечки памяти
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Принципы работы LeakCanary
LeakCanary — это библиотека для автоматического обнаружения утечек памяти в Android-приложениях. Её работа основана на нескольких ключевых принципах: отслеживание жизненного цикла объектов, сбор мусора (Garbage Collection, GC), анализ достижимости объектов и дампинг кучи (Heap Dump).
Основные этапы обнаружения утечек
-
Отслеживание уничтожения жизненно-важных объектов LeakCanary автоматически следит за объектами, которые имеют чёткий жизненный цикл (например,
Activity,Fragment,View,ViewModel). Когда такие объекты должны быть уничтожены (послеonDestroy()), LeakCanary помещает слабую ссылку (WeakReference) на них в специальный очередь и инициирует сборку мусора. -
Принудительный запуск сборки мусора Библиотека явно вызывает
Runtime.getRuntime().gc()(с дополнительными паузами для надёжности), чтобы гарантировать, что объекты, на которые остались только слабые ссылки, будут удалены. Если после GC слабая ссылка всё ещё указывает на объект — это потенциальная утечка. -
Создание дампа кучи (Heap Dump) Если утечка обнаружена, LeakCanary создаёт дамп кучи — снимок всей памяти приложения в определённый момент. Это делается с помощью
Debug.dumpHprofData(). Дамп сохраняется на устройстве для последующего анализа. -
Анализ дампа с помощью Shark (или ранее — HaHA) Начиная с версии 2.0, LeakCanary использует собственный движок анализа Shark (ранее использовалась библиотека HaHA — Hadoop Heap Analyzer). Shark анализирует бинарный формат HPROF, строя граф достижимости объектов. Основная задача — найти цепочку ссылок, которая удерживает объект в памяти, хотя он уже должен быть удалён.
// Пример кода, демонстрирующий концепцию отслеживания class ObjectWatcher { private val watchedObjects = mutableMapOf<String, WeakReference<Any>>() private val queue = ReferenceQueue<Any>() fun watch(object: Any, description: String) { val weakRef = WeakReference(object, queue) watchedObjects[description] = weakRef ensureGoneAsync() // Запуск асинхронной проверки } private fun ensureGoneAsync() { // Выполняется в фоновом потоке: // 1. Ожидание паузы для завершения onDestroy() // 2. Вызов System.gc() // 3. Проверка, очищена ли weakRef // 4. Если нет — создание дампа кучи } } -
Построение цепочки утечки (Leak Trace) Shark ищет пути от корней сборки мусора (GC Roots) до утекающего объекта. Корни — это объекты, которые всегда доступны (например, статические поля, потоки, локальные переменные в стеке). Если существует путь от корня до объекта — сборщик мусора не может его удалить.
Пример цепочки утечки: ┬ ├─ com.example.MyActivity │ ↓ static MyActivity.instance │ ~~~~~~~~ ├─ com.example.MyPresenter │ ↓ presenter.view │ ~~~~ └─ com.example.MyActivity (утекающий объект)
Ключевые компоненты архитектуры
- ObjectWatcher — отслеживает объекты, которые должны быть собраны GC.
- HeapDumper — отвечает за создание дампа кучи в формате HPROF.
- HeapAnalyzer (Shark) — анализирует дамп, находит удерживающие цепочки ссылок.
- DisplayLeakService — отображает уведомления и подробности об утечках.
Особенности реализации
- Автоматизация: Не требует ручного запуска — утечки обнаруживаются в процессе использования приложения.
- Минимальное влияние на производительность: Анализ выполняется в фоновом потоке, дампинг происходит только при подозрении на утечку.
- Наглядность результатов: Предоставляет понятные цепочки ссылок с указанием классов, полей и строк кода (если включён ProGuard маппинг).
- Конфигурируемость: Можно настраивать отслеживаемые объекты, пороги для уведомлений, отключать дампинг в продакшене.
Практический пример настройки
// В Application классе
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
if (LeakCanary.isInAnalyzerProcess(this)) {
// Этот процесс предназначен только для LeakCanary
return
}
LeakCanary.config = LeakCanary.config.copy(
retainInstancesOf = listOf(MyViewModel::class.java),
dumpHeap = BuildConfig.DEBUG // Только в отладочной сборке
)
LeakCanary.install(this)
}
}
Как избежать ложных срабатываний
- Корректные жизненные циклы: Убедитесь, что
onDestroy()вызывается. - Очистка ссылок: В
onDestroy()обнуляйте ссылки на активности/фрагменты в синглтонах. - Использование WeakReference: Для кешей или слушателей используйте слабые ссылки.
- Учёт конфигурационных изменений: Активности могут пересоздаваться без утечек.
LeakCanary существенно упрощает отладку утечек памяти, превращая сложную задачу анализа дампов кучи в автоматизированный процесс с понятными результатами, что экономит часы разработки и улучшает стабильность приложений.