Если вам нужно проанализировать использование памяти, начните с инструментов оценки высокого уровня, чтобы определить, какие категории памяти наиболее важны.
dumpsys meminfo
Самый распространенный способ получить обзор использования памяти приложением – это dumpsys meminfo. Чтобы посмотреть подробную разбивку, включая все сопоставления памяти, часто бывает полезно включить флаг -a:
adb shell dumpsys meminfo -a <package_name_or_pid>
Ниже приведен пример выходных данных приложения, испытывающего нехватку памяти (обратите внимание на высокое значение SwapPss):
** MEMINFO in pid 4562 [com.android.memorylab] **
Pss Private Private SwapPss Rss Heap Heap Heap
Total Dirty Clean Dirty Total Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------
Native Heap 394 268 64 17440 4264 29416 18128 8040
Dalvik Heap 165 8 0 672 8196 40949 32757 8192
Dalvik Other 47 28 0 604 1132
Stack 112 108 4 312 120
...
TOTAL 33197 580 788 21920 167776 70365 50885 16232
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 12 32608
Native Heap: 268 4264
Code: 724 125672
Stack: 108 120
Graphics: 0 0
Private Other: 256
System: 31829
Unknown: 5112
TOTAL PSS: 33197 TOTAL RSS: 167776 TOTAL SWAP PSS: 21920
Objects
Views: 15 ViewRootImpl: 1
AppContexts: 5 Activities: 1
...
Интерпретация данных
- Pss Total – пропорциональный размер набора. Это сумма
Private Dirty.Private Clean+ доля памяти, которая используется совместно с другими процессами (например, загрузочным образом Zygote). Это лучший показатель того, сколько памяти занимает приложение.
- Частная измененная память. Это ОЗУ, которая используется только вашим процессом и была изменена (или является анонимной памятью). Это самый важный показатель для поиска утечек, поскольку эту память нельзя освободить.
- Частная чистая память. ОЗУ, которое используется только вашим процессом, но является неизмененной копией файла в хранилище (например, DEX-файла). ОС может удалить его, если памяти недостаточно.
- SwapPss – память, которая была сжата и перенесена в ZRAM. Это может происходить из-за нехватки памяти или потому, что среда Android Runtime определила фоновые процессы, которые нужно сжать (переместив их неиспользуемые или неактивные страницы в ZRAM). В примере с
dumpsysпочти всеNative HeapиDalvik Heapбыли перемещены в ZRAM.
- Сводка по приложениям. Более общая классификация, в которой подробные строки объединены в понятные группы.
- Объекты. Полезно для отслеживания утечек экземпляров
ActivitiesиViewRootImpl. Если вы выйдете из приложения и принудительно запустите сборку мусора, количество объектовActivitiesдолжно вернуться к нулю.
Раздел "Бюджет памяти"
В Android 18 и более поздних версиях dumpsys meminfo также выводит раздел Memory Budget, когда создает дамп одного приложения. В этом разделе содержатся статистические данные memcg для процесса и всего приложения (пакета), поэтому вам не нужно искать файлы cgroup самостоятельно. Вот как выглядит раздел из примера приложения MemoryLab на переднем плане после трех нажатий на кнопку Allocate Java Memory (10MB) (Выделить память Java (10 МБ)):
Memory Budget
Process(KB) Package(KB)
----------- -----------
Current Usage: 83820 87976
Limit: none 4718592
Limit Source: none MemoryLimiter
Peak Usage: 85136 89292
Swap Used: 0 0
Usage Breakdown
Anonymous: 81700 81700
File: 96 4200
File Mapped: 16 616
Shared Memory: 16 16
Kernel: 1580 1632
Page Tables: 640 640
Slab: 439 458
Pressure & Reclaim
Throttle Events: 0 0
Refaults (Anon): 0 0
Refaults (File): 0 0
- Текущее использование. Память, выделенная для memcg (
memory.current). Это значение ограничивается бюджетом памяти. В примере он увеличился примерно на 36 700 КБ после трех выделений памяти Java по 10 МБ, что соответствует TOTAL PSS. Общий объем памяти пакета превышает общий объем памяти процесса даже для приложения с одним процессом, если кэшированные страницы файлов из предыдущего запуска ещё не были освобождены. - Limit (Ограничение). Порог возврата (
memory.high), который применяется в настоящее время, илиnone, если он не задан. В примере у пакета есть ограничение на системную память, а у процесса – нет. - Источник ограничения. Откуда взялось ограничение.
AndroidManifest– это<memory-budget>, объявленный в манифесте приложения,MemoryBudgetManager– бюджет, заданный приложением во время выполнения,MemoryLimiter– ограничение системной памяти, аam memory-limiter– переопределение, заданное вручную с помощьюadb. - Пиковое использование. Максимальный объем памяти, использованный с момента создания memcg (
memory.peak). Если ядро не поддерживает этот параметр, показываетсяN/A. - Файл подкачки. Память, сжатая в ZRAM (
memory.swap.current). - Разбивка по использованию. Основные поля
memory.stat, такие как анонимная память и память, поддерживаемая файлами. - Нагрузка и освобождение памяти. События регулирования – сколько раз memcg превышал лимит
memory.high. Повторные ошибки – это количество страниц, которые были выгружены или удалены, а затем снова потребовались. Если эти числа растут во время использования приложения, значит, лимит меньше, чем рабочий набор приложения.
Если вы используете флаг -a или -v, в разделе также будут перечислены все необработанные счетчики из memory.stat и memory.events.
Практическое упражнение: быстрая сортировка с помощью meminfo
Это упражнение показывает, как разные типы распределения отображаются в meminfo.
Запустите приложение.
adb shell am start -W -n com.android.memorylab/.MainActivityПроверьте базовый объем памяти
adb shell dumpsys meminfo -s com.android.memorylab(Параметр
-sпозволяет получить краткую сводку по категориям памяти.)Выделение памяти Java. Нажмите кнопку Allocate Java Memory (10MB) (Выделить память Java (10 МБ)) три раза.
Проверьте изменения. Снова запустите
meminfo -s. Значение Java Heap должно увеличиться примерно на 30 000 КБ (30 МБ).Allocate Native Memory (Выделить нативную память). Нажмите кнопку Allocate Native Memory (1GB Incompressible) (Выделить нативную память (1 ГБ несжимаемой)) один раз.
Проверьте изменения. Снова запустите
meminfo -s. Значение Native Heap должно значительно увеличиться (примерно до 1 000 000 КБ).Сравните с memcg (Android 18 и более поздние версии). Посмотрите раздел Memory Budget (Бюджет памяти) в конце выходных данных
meminfo -s. Текущее использование растет вместе с Общим PSS после каждого выделения, поскольку и Java, и нативные выделения относятся к memcg приложения.
Глобальный вид
Чтобы посмотреть использование памяти во всей системе, выполните следующую команду:
adb shell dumpsys meminfo
Вы увидите сводку по использованию ОЗУ, включая общий объем ОЗУ, объем свободной ОЗУ и список процессов, отсортированных по PSS.
Ключевые системные показатели
- ZRAM – сжатый файл подкачки в ОЗУ.
- DMA-BUF – графические буферы и буферы общей памяти. Современные версии Android активно используют DMA-BUF для рендеринга пользовательского интерфейса. Если значение
DMA-BUFочень высокое, изучите процессы, связанные с графикой, с помощью/sys/kernel/debug/dma_buf/bufinfo. - Потерянная ОЗУ. Память, которая не учитывается ядром или каким-либо процессом (например, зарезервированные аппаратные области).
Статистика memcg
У каждого процесса приложения есть собственная контрольная группа памяти (memcg), и ядро ведет учет выделенной ему памяти (о том, чем этот учет отличается от RSS и PSS, читайте в статье memcg). Чтение этих файлов не требует больших ресурсов и не мешает работе приложения, поэтому вы можете считывать их каждую секунду во время выполнения пути пользователя.
Найдите путь memcg процесса в строке 0:: файла /proc/<PID>/cgroup, а не создавайте его самостоятельно, поскольку макет cgroup может отличаться на разных устройствах. Передайте каждую команду устройства в adb shell в виде одной строки в кавычках, чтобы оболочка устройства видела кавычки:
PID=$(adb shell pidof -s <package_name>)
CG=/sys/fs/cgroup$(adb shell "sed -n 's/^0:://p' /proc/$PID/cgroup")
adb shell "cat $CG/memory.current $CG/memory.swap.current"
adb shell "grep -E '^(anon|file|workingset_refault_.*) ' $CG/memory.stat"
Ключевые файлы:
memory.current– память, выделенная процессу, в байтах. Именно это значение ограничивается бюджетом памяти.memory.swap.current– память, сжатая в ZRAM, в байтах.memory.stat– разбивка платежа, в байтах для размеров.anon– анонимная память, например кучи Java и нативного кода.file– это кеш страниц, поддерживаемый файлами, например кодом и ресурсами.workingset_refault_anonиworkingset_refault_file– количество страниц, которые были выгружены или удалены, а затем снова потребовались. Растущее количество повторных ошибок означает, что процессу не хватает памяти.memory.events: количество событий, связанных с ограничением памяти.high– сколько раз процесс был ограничен из-за превышения лимитаmemory.high.
Родительский каталог memcg процесса принадлежит всему приложению (его UID). В его файлах содержится память всех запущенных процессов приложения, а также кешированные страницы файлов, оставшиеся от предыдущих процессов приложения, которые были завершены и ещё не восстановлены:
adb shell "cat $CG/../memory.current"
Если появится ошибка, связанная с разрешениями, выполните команду adb root и повторите попытку. Некоторые устройства позволяют читать эти файлы только от имени пользователя root.
Чтобы узнать, как работать с этими файлами, ознакомьтесь с практическим руководством по memcg, в котором используется пример приложения MemoryLab.
showmap
Чтобы получить более подробную информацию о сопоставлениях памяти процесса, используйте showmap. Этот инструмент считывает данные из /proc/<pid>/smaps.
adb shell showmap <pid>
showmap перечисляет все карты памяти в процессе. Вот на какие сопоставления клавиш стоит обратить внимание:
[anon:scudo:primary]или[anon:libc_malloc]– нативная куча. В современных версиях Android используется распределитель Scudo.*.art– образы кучи Java (например, загрузочный образ).[anon:dalvik-main space]– основная куча Java.*.so– общие библиотеки.*.{oat,dex,odex,vdex}– скомпилированный код DEX.
Вы можете отсортировать вывод, чтобы найти самые большие потребители частной памяти:
# Sort by private clean + private dirty memory
adb shell showmap <pid> | awk '{print $6+$7, $0}' | sort -rn | head -n 10
Пример результата:
virtual shared shared private private Anon Shmem File Shared Private
size RSS PSS clean dirty clean dirty swap swapPSS HugePages PmdMapped PmdMapped Hugetlb Hugetlb Locked # object
-------- -------- -------- -------- -------- -------- -------- -------- -------- --------- --------- --------- -------- -------- -------- ---- ------------------------------
2776 18176 5784 2823 0 3008 308 2468 4700 4700 0 0 0 0 0 0 47 [anon:scudo:primary]
560 952 876 657 316 0 560 0 12 12 0 0 0 0 0 0 8 /data/app/.../base.apk
448 393216 448 448 0 0 0 448 0 0 0 0 0 0 0 0 2 [anon:dalvik-main space]
...
Практическое упражнение: подробный анализ с помощью showmap
Узнайте, как определить конкретные сопоставления памяти и различить чистую и грязную память.
- Mmap Clean Memory. Нажмите кнопку Mmap Clean Anon Memory (10MB) в приложении MemoryLab один раз.
Получите идентификатор процесса. Идентификатор процесса указан в интерфейсе приложения. Вы также можете найти его с помощью этой команды:
adb shell pidof com.android.memorylabПроверка с помощью showmap
# Replace <PID> with the actual process ID from the previous step adb shell showmap <PID> | grep "memorylab-clean"Найдите сопоставление. Вы должны увидеть анонимное сопоставление с меткой
[anon:memorylab-clean]и виртуальным размером ровно 10 240 КБ. Обратите внимание, что значение private dirty для этой карты, скорее всего, равно 0. Это "чистая" анонимная память, поскольку мы использовалиmmap, чтобы зарезервировать виртуальное пространство, но ещё ничего в него не записали (поэтому физическая ОЗУ не используется).Сравнение нативных куч. Сравните это с сопоставлениями
[anon:scudo:primary], которые появились бы вshowmap, если бы вы запустили его после шага 5 упражненияmeminfo. Поскольку этот 1 ГБ нативной памяти фактически записывается на каждую страницу, эти страницы являются "грязными" и будут отображаться в столбце private dirty.Совет (крупные объекты). Обратите внимание на выделение памяти в куче Java из упражнения
meminfo. Поскольку это массивы байтов размером 10 МБ, вshowmapони, скорее всего, будут представлены как[anon:dalvik-free list large object space]. Android Runtime (ART) помещает объекты размером более 12 КБ в отдельное пространство для больших объектов (LOS), чтобы избежать фрагментации в основной куче.
procstats
Если meminfo предоставляет моментальный снимок, то procstats – статистику, периодически собирая данные о состоянии и использовании памяти всех запущенных приложений. Это основной инструмент для долгосрочного мониторинга состояния памяти, который используется для создания отчетов об использовании памяти в настройках Android.
# View stats for the last 3 hours
adb shell dumpsys procstats --hours 3
Что означают статусы
Результаты работы procstats могут быть слишком объемными. Самая важная часть – это показатели в скобках рядом с каждым состоянием процесса:
Top: 100% (54MB-64MB-67MB / 49MB-58MB-61MB / 110MB-120MB-130MB over 4)
Формат: (% времени в состоянии) (minPss-avgPss-maxPss / minUss-avgUss-maxUss / minRss-avgRss-maxRss по выборкам)
- Pss (Proportional Set Size) – общая память делится пропорционально между процессами.
- Uss (Unique Set Size) – память, используемая только этим процессом (эквивалентна Private Clean + Private Dirty).
- Rss (Resident Set Size) – общий объем используемой физической памяти, включая все общие страницы (может быть завышен).
Почему это важно
- Стабильное приложение. Минимальное, среднее и максимальное значения близки друг к другу.
- Утечка памяти. Максимальное значение значительно выше минимального, а среднее значение со временем растет по мере увеличения количества выборок.
- Скачкообразное использование. Максимальное значение намного выше среднего. Это указывает на временные тяжелые операции (например, обработку изображений), которые могут привести к ошибкам нехватки памяти, если они происходят в неподходящее время.
Состояния процесса
procstats отслеживает приложения в нескольких штатах (часто сокращенно):
- Persist. Критически важные системные процессы, которые выполняются постоянно.
- Top. Приложение находится на переднем плане и видно пользователю.
- ImpFg (важные активные службы) – критически важные фоновые процессы (например, IME) или приложения с активной службой.
- ImpBg (Important Background) – фоновые процессы, которые считаются важными для системы.
- Fgs/BFgs – активные службы или связанные активные службы.
- Service/ServRst – фоновые службы или службы, которые перезапускаются.
- Cached/LastAct – приложения, которые кешируются в фоновом режиме для быстрого перезапуска. Их можно безопасно завершить.
Состояние памяти устройства и статистика времени работы
procstats также отслеживает, сколько времени все устройство работало в условиях нехватки памяти. Найдите раздел Run time Stats (Статистика времени выполнения).
Run time Stats:
SOff/Norm: +1h10m15s
SOn/Norm: +15m20s
Low: +2m10s
Crit: +45s
- SOff/SOn – экран выключен или включен.
- Norm / Mod / Low / Crit – уровни нагрузки на память (нормальный, умеренный, низкий, критический).
Если устройство долго находится в состоянии Low или Crit, оно будет работать медленно, поскольку ядро активно освобождает память, а LMK начинает завершать фоновые процессы. Затем вы можете просмотреть статистику по пакетам, чтобы узнать, какие приложения активно используют память, когда устройство находится в этих состояниях.
← Основные понятия | ↑ Наверх | Java →