Профилировщик памяти — это компонент профилировщика Android , который помогает выявлять утечки и оттоки памяти, которые могут привести к зависаниям, зависаниям и даже сбоям приложений. Он показывает график использования памяти вашим приложением в реальном времени и позволяет вам создавать дамп кучи, принудительно выполнять сборку мусора и отслеживать распределение памяти.
Чтобы открыть профилировщик памяти, выполните следующие действия:
- Нажмите «Просмотр» > «Окна инструментов» > «Профилировщик» (вы также можете нажать «Профиль») .
на панели инструментов).
- Выберите устройство и процесс приложения, которые вы хотите профилировать, на панели инструментов Android Profiler. Если вы подключили устройство через USB, но не видите его в списке, убедитесь, что у вас включена отладка по USB .
- Щелкните в любом месте временной шкалы ПАМЯТИ , чтобы открыть Профилировщик памяти.
Кроме того, вы можете проверить память вашего приложения из командной строки с помощью dumpsys , а также просмотреть события GC в logcat .
Почему вам следует профилировать память приложения
Android предоставляет среду управляемой памяти : когда он определяет, что ваше приложение больше не использует некоторые объекты, сборщик мусора освобождает неиспользуемую память обратно в кучу. То, как Android находит неиспользуемую память, постоянно совершенствуется, но в какой-то момент во всех версиях Android система должна ненадолго приостановить ваш код. Большую часть времени паузы незаметны. Однако если ваше приложение выделяет память быстрее, чем система может ее собрать, ваше приложение может задерживаться, пока сборщик не освободит достаточно памяти для удовлетворения ваших выделений. Задержка может привести к тому, что ваше приложение пропустит кадры и станет заметной медлительностью.
Даже если ваше приложение не проявляет медлительности, если в нем происходит утечка памяти, оно может сохранить эту память, даже находясь в фоновом режиме. Такое поведение может замедлить остальную производительность памяти системы из-за ненужных событий сборки мусора. В конце концов, система вынуждена завершить процесс вашего приложения, чтобы освободить память. Затем, когда пользователь возвращается в ваше приложение, оно должно полностью перезапуститься.
Чтобы предотвратить эти проблемы, вам следует использовать профилировщик памяти для выполнения следующих действий:
- Найдите на временной шкале нежелательные шаблоны распределения памяти, которые могут вызывать проблемы с производительностью.
- Дамп кучи Java, чтобы увидеть, какие объекты используют память в любой момент времени. Несколько дампов кучи в течение длительного периода времени могут помочь выявить утечки памяти.
- Записывайте выделение памяти во время обычного и интенсивного взаимодействия с пользователем, чтобы точно определить, где ваш код либо выделяет слишком много объектов за короткое время, либо выделяет объекты, которые стали утечкой.
Информацию о методах программирования, которые могут сократить использование памяти вашим приложением, можно найти в статье «Управление памятью вашего приложения» .
Обзор профилировщика памяти
Когда вы впервые откроете профилировщик памяти, вы увидите подробную временную шкалу использования памяти вашим приложением и получите доступ к инструментам для принудительной сборки мусора, создания дампа кучи и записи распределения памяти.
Рисунок 1. Профилировщик памяти
Как показано на рисунке 1, представление по умолчанию для профилировщика памяти включает следующее:
- Кнопка для принудительного события сборки мусора.
Кнопка для захвата дампа кучи .
Примечание. Кнопка записи распределения памяти появляется справа от кнопки дампа кучи только при подключении к устройству под управлением Android 7.1 (уровень API 25) или ниже.
- Раскрывающееся меню, позволяющее указать, как часто профилировщик фиксирует распределение памяти. Выбор подходящего параметра может помочь вам повысить производительность приложения при профилировании .
- Кнопки для увеличения/уменьшения масштаба временной шкалы.
- Кнопка для перехода к текущим данным памяти.
- Временная шкала событий, на которой показаны состояния активности, события пользовательского ввода и события поворота экрана.
- Временная шкала использования памяти, которая включает в себя следующее:
- Составной график, показывающий, сколько памяти используется каждой категорией памяти, как показано осью Y слева и цветовой клавишей вверху.
- Пунктирная линия указывает количество выделенных объектов, как указано по оси Y справа.
- Значок для каждого события сбора мусора.
Однако если вы используете устройство под управлением Android 7.1 или более ранней версии, по умолчанию не все данные профилирования отображаются. Если вы видите сообщение «Расширенное профилирование недоступно для выбранного процесса», вам необходимо включить расширенное профилирование, чтобы увидеть следующее:
- Хронология событий
- Количество выделенных объектов
- Мероприятия по сбору мусора
В Android 8.0 и более поздних версиях расширенное профилирование всегда включено для отлаживаемых приложений.
Как считается память
Числа, которые вы видите в верхней части профилировщика памяти (рис. 2), основаны на всех страницах частной памяти, зафиксированных вашим приложением в соответствии с системой Android. В это число не входят страницы, которыми поделились система или другие приложения.
Рис. 2. Обозначение количества памяти в верхней части профилировщика памяти.
Категории в подсчете памяти следующие:
- Java : память объектов, выделенных из кода Java или Kotlin.
Native : память объектов, выделенных из кода C или C++.
Даже если вы не используете C++ в своем приложении, вы можете увидеть, что здесь используется некоторая встроенная память, поскольку платформа Android использует встроенную память для выполнения различных задач от вашего имени, например, при обработке ресурсов изображений и другой графики, даже если код, который вы Я написал на Java или Kotlin.
Графика : память, используемая для очередей графического буфера для отображения пикселей на экране, включая поверхности GL, текстуры GL и т. д. (Обратите внимание, что это память, используемая совместно с ЦП, а не выделенная память графического процессора.)
Стек : память, используемая как собственным стеком, так и стеком Java в вашем приложении. Обычно это связано с количеством потоков, выполняемых вашим приложением.
Код : память, которую ваше приложение использует для кода и ресурсов, таких как байт-код dex, оптимизированный или скомпилированный код dex, библиотеки .so и шрифты.
Другое : память, используемая вашим приложением, которую система не знает, как классифицировать.
Выделено : количество объектов Java/Kotlin, выделенных вашим приложением. При этом не учитываются объекты, выделенные в C или C++.
При подключении к устройству под управлением Android 7.1 и более ранней версии отсчет выделения начинается только в момент подключения профилировщика памяти к работающему приложению. Таким образом, любые объекты, выделенные до начала профилирования, не учитываются. Однако Android 8.0 и более поздних версий включает в себя инструмент профилирования на устройстве, который отслеживает все выделения, поэтому это число всегда представляет общее количество объектов Java, находящихся в обращении в вашем приложении на Android 8.0 и более поздних версиях.
По сравнению с подсчетом памяти в предыдущем инструменте Android Monitor, новый Memory Profiler записывает вашу память по-другому, поэтому может показаться, что использование памяти теперь выше. Профилировщик памяти отслеживает некоторые дополнительные категории, которые увеличивают общее количество, но если вас интересует только память кучи Java, то число «Java» должно быть аналогично значению из предыдущего инструмента. Хотя номер Java, вероятно, не совсем соответствует тому, что вы видели в Android Monitor, новый номер учитывает все страницы физической памяти, которые были выделены в куче Java вашего приложения с момента его разветвления от Zygote. Таким образом, это дает точное представление о том, сколько физической памяти фактически использует ваше приложение.
Просмотр распределения памяти
Распределение памяти показывает , как был распределен каждый объект Java и ссылка JNI в вашей памяти. В частности, профилировщик памяти может показать вам следующее о распределении объектов:
- Какие типы объектов были выделены и сколько места они занимают.
- Трассировка стека каждого выделения, включая поток.
- Когда объекты были освобождены (только при использовании устройства с Android 8.0 или выше).
Чтобы записать выделения Java и Kotlin, выберите «Запись выделений Java/Kotlin» , затем выберите «Запись» . Если устройство работает под управлением Android 8 или более поздней версии, пользовательский интерфейс Memory Profiler переходит на отдельный экран, отображающий текущую запись. Вы можете взаимодействовать с мини-шкалой времени над записью (например, чтобы изменить диапазон выбора). Чтобы завершить запись, выберите «Стоп». .
В Android 7.1 и более ранних версиях профилировщик памяти использует устаревшую запись распределения, которая отображает запись на временной шкале до тех пор, пока вы не нажмете « Стоп» .
После выбора области временной шкалы (или завершения сеанса записи на устройстве под управлением Android 7.1 или более ранней версии) появляется список выделенных объектов, сгруппированный по имени класса и отсортированный по количеству их кучи.
Чтобы проверить запись распределения, выполните следующие действия:
- Просмотрите список, чтобы найти объекты с необычно большим количеством кучи, которые могут стать причиной утечки. Чтобы облегчить поиск известных классов, щелкните заголовок столбца «Имя класса» для сортировки в алфавитном порядке. Затем щелкните имя класса. Справа появится панель «Просмотр экземпляра» , на которой будет показан каждый экземпляр этого класса, как показано на рисунке 3.
- Кроме того, вы можете быстро найти объекты, нажав «Фильтр».
или нажав Control+F (Command+F на Mac) и введя имя класса или пакета в поле поиска. Вы также можете выполнить поиск по имени метода, если в раскрывающемся меню выберите «Упорядочить по стеку вызовов» . Если вы хотите использовать регулярные выражения, установите флажок рядом с Regex . Установите флажок « Учитывать регистр» , если ваш поисковый запрос чувствителен к регистру.
- Кроме того, вы можете быстро найти объекты, нажав «Фильтр».
- На панели «Просмотр экземпляра» щелкните экземпляр. Ниже появится вкладка «Стек вызовов» , показывающая, где был выделен этот экземпляр и в каком потоке.
- На вкладке «Стек вызовов» щелкните правой кнопкой мыши любую строку и выберите « Перейти к исходному коду» , чтобы открыть этот код в редакторе.
Рис. 3. Подробная информация о каждом выделенном объекте отображается в представлении экземпляра справа.
Вы можете использовать два меню над списком выделенных объектов, чтобы выбрать, какую кучу проверять и как организовать данные.
В меню слева выберите кучу для проверки:
- куча по умолчанию : когда система не указывает кучи.
- куча образа : образ загрузки системы, содержащий классы, предварительно загруженные во время загрузки. Выделения здесь гарантированно никогда не сместятся и не исчезнут.
- Куча зиготы : куча копирования при записи, из которой в системе Android создается ответвление процесса приложения.
- куча приложения : основная куча, в которой ваше приложение выделяет память.
- Куча JNI : куча, которая показывает, где выделяются и освобождаются ссылки на собственный интерфейс Java (JNI).
В меню справа выберите способ распределения:
- Упорядочить по классу : группирует все распределения по имени класса. Это значение по умолчанию.
- Упорядочить по пакетам : группирует все выделения по имени пакета.
- Упорядочить по стеку вызовов : группирует все выделения в соответствующий стек вызовов.
Улучшите производительность приложения во время профилирования
Чтобы повысить производительность приложения во время профилирования, профилировщик памяти по умолчанию периодически производит выборку выделенной памяти. При тестировании на устройствах с API уровня 26 или выше вы можете изменить это поведение, используя раскрывающийся список «Отслеживание распределения» . Доступны следующие варианты:
- Полный : фиксирует все выделения объектов в памяти. Это поведение по умолчанию в Android Studio 3.2 и более ранних версиях. Если у вас есть приложение, которое выделяет много объектов, вы можете наблюдать видимые замедления работы вашего приложения во время профилирования.
- Выборка : выборка выделений объектов в памяти через регулярные промежутки времени. Это опция по умолчанию, которая оказывает меньшее влияние на производительность приложения при профилировании. Приложения, которые выделяют большое количество объектов за короткий промежуток времени, по-прежнему могут демонстрировать заметное замедление.
- Выкл .: прекращение отслеживания выделения памяти вашим приложением.
Просмотр глобальных ссылок JNI
Java Native Interface (JNI) — это платформа, которая позволяет коду Java и собственному коду вызывать друг друга.
Ссылки JNI управляются собственным кодом вручную, поэтому объекты Java, используемые собственным кодом, могут сохраняться слишком долго. Некоторые объекты в куче Java могут стать недоступными, если ссылка JNI отбрасывается без предварительного явного удаления. Кроме того, можно исчерпать глобальный лимит ссылок JNI.
Для устранения таких проблем используйте представление кучи JNI в профилировщике памяти, чтобы просмотреть все глобальные ссылки JNI и отфильтровать их по типам Java и собственным стекам вызовов. С помощью этой информации вы можете узнать, когда и где создаются и удаляются глобальные ссылки JNI.
Пока ваше приложение работает, выберите часть временной шкалы, которую вы хотите проверить, и выберите «Куча JNI» в раскрывающемся меню над списком классов. Затем вы можете проверить объекты в куче, как обычно, и дважды щелкнуть объекты на вкладке «Стек вызовов распределения», чтобы увидеть, где ссылки JNI выделяются и освобождаются в вашем коде, как показано на рисунке 4.
Рисунок 4. Просмотр глобальных ссылок JNI
Чтобы проверить распределение памяти для кода JNI вашего приложения, вам необходимо развернуть приложение на устройстве под управлением Android 8.0 или более поздней версии.
Дополнительную информацию о JNI см. в разделе Советы по JNI .
Собственный профилировщик памяти
Профилировщик памяти Android Studio включает в себя собственный профилировщик памяти для приложений, развернутых на физических и виртуальных устройствах под управлением Android 10 и более поздних версий.
Профилировщик встроенной памяти отслеживает выделение/освобождение объектов в машинном коде за определенный период времени и предоставляет следующую информацию:
- Распределения: количество объектов, выделенных с помощью
malloc()
илиnew
оператора в течение выбранного периода времени. - Освобождения: количество объектов, освобожденных с помощью
free()
или оператораdelete
в течение выбранного периода времени. - Размер выделений: совокупный размер в байтах всех выделений за выбранный период времени.
- Размер освобождений: совокупный размер в байтах всей освобожденной памяти за выбранный период времени.
- Общее количество: значение в столбце «Распределения» минус значение в столбце «Освобождение» .
- Оставшийся размер: значение в столбце «Размер выделений» минус значение в столбце «Размер освобождений» .
Чтобы записать собственные выделения на устройствах под управлением Android 10 и более поздних версий, выберите «Записать собственные выделения» , затем выберите «Запись» . Запись продолжается до тех пор, пока вы не нажмете « Стоп». , после чего пользовательский интерфейс профилировщика памяти переходит на отдельный экран, отображающий собственную запись.
В Android 9 и более ранних версиях параметр «Запись собственных выделений» недоступен.
По умолчанию Native Memory Profiler использует размер выборки 32 байта: каждый раз, когда выделяется 32 байта памяти, создается снимок памяти. Меньший размер выборки приводит к более частому созданию снимков и дает более точные данные об использовании памяти. Больший размер выборки дает менее точные данные, но потребляет меньше ресурсов вашей системы и повышает производительность при записи.
Чтобы изменить размер выборки Native Memory Profiler:
- Выберите «Выполнить» > «Изменить конфигурации» .
- Выберите модуль приложения на левой панели.
- Перейдите на вкладку «Профилирование» и введите размер выборки в поле «Интервал выборки собственной памяти (байты)» .
- Создайте и снова запустите свое приложение.
Захват дампа кучи
Дамп кучи показывает, какие объекты вашего приложения используют память в момент создания дампа кучи. Дамп кучи, особенно после расширенного сеанса пользователя, может помочь выявить утечки памяти, показывая объекты, все еще находящиеся в памяти, которых, по вашему мнению, там больше не должно быть.
После создания дампа кучи вы можете просмотреть следующее:
- Какие типы объектов выделило ваше приложение и сколько каждого из них.
- Сколько памяти использует каждый объект.
- Где в вашем коде хранятся ссылки на каждый объект.
- Стек вызовов, в котором был выделен объект. (Стеки вызовов в настоящее время доступны с дампом кучи только в Android 7.1 и более ранних версиях, когда вы записываете дамп кучи во время записи выделений.)
Чтобы записать дамп кучи, нажмите «Создать дамп кучи» , затем выберите «Запись» . При сбросе кучи объем памяти Java может временно увеличиться. Это нормально, поскольку дамп кучи происходит в том же процессе, что и ваше приложение, и для сбора данных требуется некоторый объем памяти.
После того как профилировщик завершит запись дампа кучи, пользовательский интерфейс профилировщика памяти перейдет на отдельный экран, отображающий дамп кучи.
Рисунок 5. Просмотр дампа кучи.
Если вам нужно уточнить время создания дампа, вы можете создать дамп кучи в критической точке кода вашего приложения, вызвав dumpHprofData()
.
В списке классов вы можете увидеть следующую информацию:
- Выделения : количество выделений в куче.
Собственный размер : общий объем собственной памяти, используемой этим типом объекта (в байтах). Этот столбец виден только для Android 7.0 и выше.
Здесь вы увидите память для некоторых объектов, выделенных в Java, поскольку Android использует собственную память для некоторых классов платформы, таких как
Bitmap
.Небольшой размер : общий объем памяти Java, используемый этим типом объекта (в байтах).
Сохраненный размер : общий размер памяти, сохраняемый всеми экземплярами этого класса (в байтах).
Вы можете использовать два меню над списком выделенных объектов, чтобы выбрать, какие дампы кучи проверять и как организовать данные.
В меню слева выберите кучу для проверки:
- куча по умолчанию : когда система не указывает кучи.
- куча приложения : основная куча, в которой ваше приложение выделяет память.
- куча образа : образ загрузки системы, содержащий классы, предварительно загруженные во время загрузки. Выделения здесь гарантированно никогда не сместятся и не исчезнут.
- Куча зиготы : куча копирования при записи, из которой в системе Android создается ответвление процесса приложения.
В меню справа выберите способ распределения:
- Упорядочить по классу : группирует все распределения по имени класса. Это значение по умолчанию.
- Упорядочить по пакету : группирует все выделения по имени пакета.
- Упорядочить по стеку вызовов : группирует все выделения в соответствующий стек вызовов. Этот параметр работает только в том случае, если вы записываете дамп кучи во время записи выделений. Несмотря на это, в куче, скорее всего, найдутся объекты, которые были выделены до начала записи, поэтому эти выделения отображаются первыми и просто перечисляются по имени класса.
По умолчанию список сортируется по столбцу «Сохраненный размер» . Чтобы выполнить сортировку по значениям в другом столбце, щелкните заголовок столбца.
Щелкните имя класса, чтобы открыть окно «Просмотр экземпляра» справа (показано на рисунке 6). Каждый указанный экземпляр включает в себя следующее:
- Глубина : кратчайшее количество переходов от любого корня сборщика мусора до выбранного экземпляра.
- Собственный размер : размер этого экземпляра во встроенной памяти. Этот столбец виден только для Android 7.0 и выше.
- Мелкий размер : размер этого экземпляра в памяти Java.
- Сохраняемый размер : размер памяти, в которой доминирует этот экземпляр (согласно дереву доминаторов ).
Рисунок 6. Длительность, необходимая для захвата дампа кучи, указана на временной шкале.
Чтобы проверить кучу, выполните следующие действия:
- Просмотрите список, чтобы найти объекты с необычно большим количеством кучи, которые могут стать причиной утечки. Чтобы найти известные классы, щелкните заголовок столбца «Имя класса» для сортировки в алфавитном порядке. Затем щелкните имя класса. Справа появится панель «Просмотр экземпляра» , на которой будет показан каждый экземпляр этого класса, как показано на рисунке 6.
- Кроме того, вы можете быстро найти объекты, нажав «Фильтр».
или нажав Control+F (Command+F на Mac) и введя имя класса или пакета в поле поиска. Вы также можете выполнить поиск по имени метода, если в раскрывающемся меню выберите «Упорядочить по стеку вызовов» . Если вы хотите использовать регулярные выражения, установите флажок рядом с Regex . Установите флажок « Учитывать регистр» , если ваш поисковый запрос чувствителен к регистру.
- Кроме того, вы можете быстро найти объекты, нажав «Фильтр».
- На панели «Просмотр экземпляра» щелкните экземпляр. Ниже появится вкладка «Ссылки» , на которой показаны все ссылки на этот объект.
Или щелкните стрелку рядом с именем экземпляра, чтобы просмотреть все его поля, а затем щелкните имя поля, чтобы просмотреть все его ссылки. Если вы хотите просмотреть сведения об экземпляре для поля, щелкните поле правой кнопкой мыши и выберите « Перейти к экземпляру» .
- Если на вкладке «Ссылки» вы обнаружите ссылку, которая может вызывать утечку памяти, щелкните ее правой кнопкой мыши и выберите « Перейти к экземпляру» . При этом соответствующий экземпляр выбирается из дампа кучи, показывая вам его собственные данные экземпляра.
В дампе кучи найдите утечки памяти, вызванные любой из следующих причин:
- Долгоживущие ссылки на
Activity
,Context
,View
,Drawable
и другие объекты, которые могут содержать ссылку на контейнерActivity
илиContext
. - Нестатические внутренние классы, такие как
Runnable
, которые могут содержать экземплярActivity
. - Тайники, в которых хранятся объекты дольше, чем необходимо.
Сохраните дамп кучи как файл HPROF.
После создания дампа кучи данные можно просмотреть в профилировщике памяти только во время работы профилировщика. При выходе из сеанса профилирования дамп кучи теряется. Итак, если вы хотите сохранить его для просмотра позже, экспортируйте дамп кучи в файл HPROF. В Android Studio 3.1 и более ранних версиях экспорт захвата в файл кнопка находится в левой части панели инструментов под шкалой времени; в Android Studio 3.2 и более поздних версиях справа от каждой записи дампа кучи на панели «Сеансы» есть кнопка «Экспортировать дамп кучи» . В появившемся диалоговом окне «Экспортировать как» сохраните файл с расширением имени файла
.hprof
.
Чтобы использовать другой анализатор HPROF, например jhat , вам необходимо преобразовать файл HPROF из формата Android в формат Java SE HPROF. Вы можете сделать это с помощью инструмента hprof-conv
, расположенного в каталоге android_sdk /platform-tools/
. Запустите команду hprof-conv
с двумя аргументами: исходный файл HPROF и место для записи преобразованного файла HPROF. Например:
hprof-conv heap-original.hprof heap-converted.hprof
Импортировать файл дампа кучи
Чтобы импортировать файл HPROF ( .hprof
), нажмите « Начать новый сеанс профилирования». на панели «Сеансы» выберите «Загрузить из файла» и выберите файл в браузере файлов.
Вы также можете импортировать файл HPROF, перетащив его из браузера файлов в окно редактора.
Обнаружение утечек в Memory Profiler
При анализе дампа кучи в Memory Profiler вы можете фильтровать данные профилирования, которые, по мнению Android Studio, могут указывать на утечки памяти для экземпляров Activity
и Fragment
в вашем приложении.
Типы данных, отображаемые фильтром, включают следующее:
- Экземпляры
Activity
, которые были уничтожены, но на которые все еще ссылаются. - Экземпляры
Fragment
, у которых нет допустимогоFragmentManager
, но на которые все еще ссылаются.
В определенных ситуациях, например в следующих, фильтр может давать ложные срабатывания:
-
Fragment
создан, но еще не использован. -
Fragment
кэшируется, но не как частьFragmentTransaction
.
Чтобы использовать эту функцию, сначала сохраните дамп кучи или импортируйте файл дампа кучи в Android Studio. Чтобы отобразить фрагменты и действия, которые могут вызывать утечку памяти, установите флажок «Утечка активности/фрагмента» на панели дампа кучи профилировщика памяти, как показано на рисунке 7.
Рисунок 7. Фильтрация дампа кучи на наличие утечек памяти.
Методы профилирования вашей памяти
При использовании профилировщика памяти вам следует усилить код приложения и попытаться вызвать утечки памяти. Один из способов спровоцировать утечку памяти в вашем приложении — дать ему поработать некоторое время, прежде чем проверять кучу. Утечки могут проникнуть в верхнюю часть кучи. Однако чем меньше утечка, тем дольше вам нужно запускать приложение, чтобы ее увидеть.
Вы также можете вызвать утечку памяти одним из следующих способов:
- Поворачивайте устройство из книжного положения в альбомное и обратно несколько раз, находясь в разных состояниях активности. Вращение устройства часто может привести к утечке из приложения объекта
Activity
,Context
илиView
, поскольку система воссоздаетActivity
, и если ваше приложение содержит ссылку на один из этих объектов где-то еще, система не сможет его собрать мусором. - Переключайтесь между своим приложением и другим приложением, находясь в разных состояниях активности (перейдите на главный экран, затем вернитесь в свое приложение).
Совет: Вы также можете выполнить описанные выше шаги, используя среду тестирования MonkeyRunner .