Проблемы с памятью часто затрагивают множество компонентов по всей системе. Понимание того, как ядро управляет памятью и как процессы конкурируют за ресурсы, является ключом к решению сложных проблем.
Идеально для системного анализа
Perfetto — это основной инструмент для анализа всей системы. Он позволяет записывать трассировку, включающую:
- Счетчики памяти процессов :
rss.anon,rss.fileиswapдля каждого процесса. - Статистика ядра : информация из
/proc/vmstatи/proc/meminfo. - PSI (Pressure Stall Information) : Подробные показатели того, насколько сильно процессы задерживаются из-за нехватки памяти.
- События LMK : Когда и почему «Убийца с низкой памятью» принимает решение завершить процесс.
- Планирование : Сопоставление активности по освобождению памяти (
kswapd) с использованием ЦП.
Счетчики памяти в Perfetto
При просмотре трассировки разверните группу трассировки процесса и прокрутите вниз, чтобы увидеть различные счетчики виртуальной памяти.

Настройка трассировки для счетчиков памяти
Для записи этих счетчиков для каждого процесса в трассировку ваша конфигурация Perfetto ( pbtxt ) должна включать следующие источники данных:
linux.ftraceсkmem/rss_stat: Этот источник, основанный на событиях, фиксирует мгновенные изменения RSS (Resident Set Size) по мере их обновления ядром. Он предоставляет данные высокого разрешения дляrss.anon,rss.fileиswap.data_sources: { config { name: "linux.ftrace" ftrace_config { ftrace_events: "kmem/rss_stat" # ... other events } } }linux.process_stats: Этот источник, данные о котором опрашиваются, предоставляет исходное состояние памяти для всех процессов и периодически обновляется. Он необходим для просмотра абсолютных значений памяти в начале трассировки.data_sources: { config { name: "linux.process_stats" process_stats_config { scan_all_processes_on_start: true proc_stats_poll_ms: 1000 # Optional periodic polling } } }
Информация о давлении срыва потока (PSI)
Показатель PSI отображает время, которое система (или конкретный процесс) потратила на ожидание выделения ресурсов памяти.
-
some: по меньшей мере один процесс был приостановлен в ожидании доступа к памяти. -
full: Все не простаивающие процессы были остановлены одновременно. Это указывает на серьезное узкое место.
Значения PSI можно проверить через ADB:
adb shell cat /proc/pressure/memory
Low Memory Killer (LMK)
LMK отвечает за завершение процессов для освобождения памяти, когда система испытывает нагрузку. В современных версиях Android пользовательский демон lmkd записывает логи в logcat , а события oom_kill на уровне ядра записываются в dmesg .
# Check userspace LMKD
adb logcat | grep -i "lmkd"
# Check kernel OOM killer
adb shell dmesg | grep -i "oom_kill"
В Perfetto события LMK отображаются в виде маркеров в общесистемных треках. Каждое событие включает PID завершенного процесса и причину (например, «кэш слишком мал»).
Освобождение ядра: подкачка и вытеснение
Когда в системе заканчивается свободная оперативная память, ядро должно найти способы освободить место для новых выделений. Оно делает это с помощью двух основных механизмов: подкачки анонимной памяти и вытеснения страниц, хранящихся в файлах.
Анонимная память и ZRAM
Анонимная память (куча Java, нативная куча, стеки) не имеет соответствующего файла в хранилище. Android использует ZRAM — сжатое пространство подкачки в оперативной памяти — для управления этим.
- Сжатие : Ядро идентифицирует неактивные анонимные страницы и сжимает их.
- Замена страниц : сжатые страницы перемещаются в область ZRAM.
- Подкачка : Когда процесс обращается к странице ZRAM, ядро декомпрессирует её и помещает обратно в обычную оперативную память.
На Android стандартные счетчики подкачки Linux, такие как pswpin и pswpout специально отслеживают эту активность в ZRAM, поскольку ZRAM настроен как основное (и обычно единственное) устройство подкачки.
Проверка состояния ZRAM
Итоговые данные по всему миру : используйте
/proc/meminfoчтобы узнать, сколько ZRAM сконфигурировано и сколько используется в данный момент.adb shell cat /proc/meminfo | grep Swap # Example output: # SwapCached: 0 kB # SwapTotal: 2097148 kB # SwapFree: 1850244 kBSwapTotal— это общий размер устройства ZRAM.SwapTotal - SwapFree— это объем сжатых данных, хранящихся в данный момент в ZRAM.Коэффициент сжатия : Чтобы оценить эффективность сжатия, сравните исходный размер данных с их размером в сжатом виде на устройстве ZRAM.
# Original (uncompressed) size of stored data adb shell cat /sys/block/zram0/orig_data_size # 524288000 (500 MB) # Compressed size of stored data adb shell cat /sys/block/zram0/compr_data_size # 104857600 (100 MB)В этом гипотетическом примере данные сжимаются в соотношении 5:1 . Фактическое соотношение сжатия зависит от энтропии несжатых данных.
Физические накладные расходы на оперативную память : ZRAM использует оперативную память для управления сжатыми блоками.
adb shell cat /sys/block/zram0/mem_used_total # 115343360 (110 MB)Это фактический объем физической оперативной памяти, занимаемый в данный момент устройством ZRAM (сжатые данные + метаданные).
Упражнение: Коэффициенты сжатия ZRAM
В этом упражнении вы понаблюдаете, как различные типы данных влияют на эффективность ZRAM, что поможет вам развить интуитивное понимание того, чего следует ожидать при анализе памяти реальных приложений.
- Подготовка : Убедитесь, что MemoryLab запущен. Нажмите « Освободить все выделенные ресурсы» .
Базовый уровень : Обратите внимание на текущие статистические данные ZRAM в
mm_stat:adb shell cat /sys/block/zram0/mm_stat # Columns: orig_data_size, compr_data_size, mem_used_total, ...Случайные данные (примерно 1x) : Выделить собственную память (1 ГБ несжимаемой памяти) . Дождаться выгрузки данных в файл подкачки (проверьте
vmstatили подождите 10 секунд).- Наблюдение : Вы увидите, что
orig_data_sizeиcompr_data_sizeувеличиваются почти на одинаковую величину. Случайные данные обладают высокой энтропией и не могут быть сжаты. Это «наихудший» сценарий.
- Наблюдение : Вы увидите, что
Все единицы (примерно в 4 раза больше) : Нажмите « Освободить все выделенные ресурсы» , затем нажмите «Выделить собственные ресурсы (единицы 1 ГБ)» (
0xFF).- Наблюдение :
compr_data_sizeувеличится всего примерно на 250 МБ. Это идеальный случай для сжатия. Алгоритм (обычно LZO или LZ4) легко распознает повторяющийся шаблон.
- Наблюдение :
Все нули (>100-кратное соотношение) : Нажмите « Освободить все выделенные ресурсы» , затем нажмите «Выделить собственные ресурсы (1 ГБ нулей)» (
0x00).- Наблюдение : Вы увидите исключительно высокое соотношение несжатых и сжатых данных.
orig_data_sizeувеличивается на 1 ГБ, ноcompr_data_sizeиmem_used_totalпрактически не меняются. - «Секрет» : это не сжатие, а ярлык ядра. Бэкенд ZRAM (
zsmalloc) обнаруживает страницы, заполненные нулями, и пропускает сжатие. Вместо этого он помечает страницу как дубликат глобальной нулевой страницы , потребляя всего несколько байтов метаданных.
- Наблюдение : Вы увидите исключительно высокое соотношение несжатых и сжатых данных.
Практические правила ZRAM
При анализе реальной системы можно ожидать следующих типичных коэффициентов сжатия. Эти оценки предполагают стандартный размер страницы в 4 КБ и учитывают накладные расходы на управление памятью в ZRAM:
| Тип данных | Типичное соотношение | Причина |
|---|---|---|
| Нулевые страницы | >100x | Оптимизировано с помощью ярлыка ядра «Нулевая страница» (пропускает компрессор). |
| Постоянные страницы | ~4x | Повторяющиеся значения (например, 0xFF ) обеспечивают идеальное сжатие, но накладные расходы на страницу и выравнивание блоков ограничивают эффективное соотношение. |
| Текст / JSON / Логи | ~2,5x до ~3,5x | Высокая избыточность, но более высокая энтропия, чем у одного повторяющегося байта. |
| Java-куча | ~2x до ~3x | Множество мелких объектов со схожими заголовками и редкими полями. |
| Машинный код (DEX/нативный) | ~1,5x до ~2x | Инструкции написаны сложно, но имеют узнаваемые закономерности. |
| Декодированные растровые изображения (пользовательский интерфейс) | ~2x до ~3x | Эффективно при наличии больших однотонных цветовых областей (иконки, фоны). |
| Расшифрованные растровые изображения (фото) | ~1,1x до ~1,2x | Очень высокая энтропия; значения пикселей слишком сильно различаются. |
| Зашифрованные/сжатые данные | ~1x | Уже сейчас высокая энтропия; ZRAM не может сжимать дальше. |
Именно поэтому для наших основных упражнений по оптимизации нагрузки на память мы используем случайные данные : это наихудший случай для сжатия, поскольку данные обладают максимальной энтропией, поэтому подкачка этих страниц в ZRAM не увеличивает общий размер оперативной памяти и, следовательно, создает физическую нагрузку на память быстрее, чем любые другие данные.
Вытеснение из кэша страниц
Управление памятью, использующей файлы (DEX, библиотеки, ресурсы), осуществляется через кэш страниц .
- Очищенные страницы : страницы, соответствующие данным в хранилище. Ядро может мгновенно удалить (вытеснить) их.
- «Грязные страницы» : страницы, измененные в оперативной памяти, но еще не записанные обратно в хранилище. Их нельзя удалить, пока они не будут записаны.
Проверка состояния кэша страниц
Итоговые данные по всему миру :
/proc/meminfoпоказывает, сколько памяти выделено под кэш страниц.adb shell cat /proc/meminfo | grep -E "^(Cached|Active\(file\)|Inactive\(file\))" # Example output: # Cached: 1234567 kB # Active(file): 456789 kB # Inactive(file): 777778 kBЯдро предпочитает сначала вытеснять страницы,
Inactive(file). ЕслиActive(file)значительно превышает значениеInactive(file), это говорит о том, что большая часть кэша страниц активно используется.Накопительные ошибки : Отслеживайте, сколько раз системе приходилось загружать страницы из хранилища.
adb shell cat /proc/vmstat | grep -E "pgfault|pgmajfault" # Example output: # pgfault 12345678 # Total page faults (including minor/re-faults) # pgmajfault 1234 # Major faults (actually required disk I/O)«Провал памяти» — это ситуация, когда значительное время тратится на выгрузку и загрузку страниц из оперативной памяти вместо выполнения кода и продвижения к намеченной пользователем цели. Быстрое увеличение счетчика
pgmajfaultявляется убедительным признаком провалов памяти.
Анализ активности во время выполнения с помощью vmstat
Чтобы наблюдать за процессами обмена и вытеснения в режиме реального времени, используйте vmstat .
adb shell vmstat 1
# Example output:
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa st
# 1 0 246904 123456 12345 800000 0 0 120 0 1234 5678 5 2 92 1 0
Основные столбцы для анализа активности:
-
si/so: Подкачка и выгрузка из ZRAM. Ненулевые значения здесь означают, что ядро активно перемещает анонимные страницы в/из сжатого хранилища. -
bi/bo: Блокировка ввода/вывода (Block-in and Block-out). Высокое значениеbiпри нехватке памяти указывает на частые ошибки повторного кэширования страниц (thrashing). -
wa(ожидание ввода-вывода) : Процент времени, в течение которого процессор простаивал, ожидая операций ввода-вывода с диска. Высокое значениеwaуказывает на резкое снижение производительности.
kswapd и прямая регенерация
kswapd — это поток ядра, который пытается освободить память в фоновом режиме, когда объем свободной памяти падает ниже определенного порога.
- Высокая загрузка ЦП процессом
kswapd: указывает на то, что система постоянно испытывает трудности с поиском свободных страниц. - Прямое высвобождение памяти : если
kswapdне справляется, сами процессы вынуждены синхронно высвобождать память, прежде чем смогут продолжить выделение собственных ресурсов. В Perfetto это отображается как событие "Прямое высвобождение памяти".
Скрежет и повторные разломы
«Повторная ошибка» возникает, когда ядро вытесняет страницу, которая все еще активно используется. Если система постоянно вытесняет и немедленно перезагружает одни и те же страницы, это называется «перегрузка ядра ».
Интенсивная работа процессора приводит к высокому показателю ожидания ввода-вывода ( wa в таких инструментах, как top или vmstat ). Показатель ожидания ввода-вывода отражает процент времени, в течение которого процессор простаивал, ожидая завершения незавершенной операции ввода-вывода на диске (например, перезагрузки вытесненной страницы DEX). Высокий показатель wa создает ощущение низкой отзывчивости устройства, даже если общее использование ЦП кажется низким.
Настройка системы: подкачка
Параметр swappiness определяет, следует ли ядру отдавать предпочтение выгрузке анонимной памяти или удалению памяти, хранящейся в файлах.
adb shell cat /proc/sys/vm/swappiness
- Диапазон : В современных ядрах Linux (5.8 и выше) диапазон составляет от 0 до 200 .
- 0 : Ядро будет использовать файл подкачки только в крайних экстренных случаях.
- 100 : Ядро обрабатывает анонимную и файловую память одинаково.
- 200 : Ядро активно отдает предпочтение подкачке анонимной памяти в ZRAM, чтобы сохранить как можно больше памяти, поддерживаемой файлами (кэш страниц), в оперативной памяти.
- Стандартные значения для Android : Большинство устройств на базе Android настроены на высокое значение swappiness, обычно от 100 до 160 (некоторые даже используют 200 ). Это связано с тем, что подкачка ZRAM обычно быстрее, чем чтение страниц из памяти UFS или eMMC, а сохранение кэша страниц для кода и данных приложений и системы имеет решающее значение для производительности запуска приложений и общей отзывчивости системы. Сравните это с типичной настройкой на настольных и серверных машинах Linux, равной 60 , поскольку постоянное хранилище обычно работает быстрее на этих машинах.
Практическое упражнение: ошибки страниц, кэш страниц и файл подкачки.
В этом упражнении вы будете использовать MemoryLab для создания ситуации нехватки памяти и наблюдать за реакцией ядра на использование файла подкачки и вытеснения памяти с помощью Perfetto.
1. Подготовьте устройство.
Убедитесь, что ваше устройство или эмулятор имеет права root ( adb root ).
Запустите приложение и создайте большой тестовый файл (например, 500 МБ), который мы позже будем сопоставлять:
- Откройте MemoryLab .
- Нажмите «Создать тестовый файл (500 МБ)» .
Дождитесь, пока в логах появится сообщение о завершении процесса.
2. Подготовка к отслеживанию.
Чтобы убедиться, что файл загружается из хранилища, необходимо очистить существующий кэш страниц.
# Stop the app to release its existing mappings
adb shell am force-stop com.android.memorylab
# Drop all clean caches
adb shell "echo 3 > /proc/sys/vm/drop_caches"
3. Запишите трассировку Perfetto.
Используйте конфигурацию, которая фиксирует счетчики vmstat и события кэширования страниц.
Запустить фоновую трассировку Perfetto, которая будет захватывать счетчики vmstat и события кэширования страниц:
adb shell perfetto -c - --txt \
-o /data/misc/perfetto-traces/swap_exercise.perfetto-trace --background <<EOF
buffers: { size_kb: 131072 }
data_sources: {
config {
name: "linux.sys_stats"
sys_stats_config { vmstat_period_ms: 250 }
}
}
duration_ms: 60000
EOF
4. Вызвать давление памяти.
- Запустите MemoryLab .
- Нажмите Mmap thrash_test.bin (файл размером 500 МБ) . Это позволит сопоставить наш пользовательский файл.
- Нажмите кнопку «Выделить собственную память (1 ГБ несжимаемой памяти)» несколько раз. Продолжайте, пока устройство не начнет тормозить. Это заставит ядро заменить анонимную память на ZRAM и в конечном итоге удалить наш отображенный файл из кэша.
- Tap Thrash Pagecache (Refault test) . Эта команда многократно считывает сопоставленный файл, вызывая повторные ошибки, если он был удален.
5. Анализируем трассировку в Perfetto
Откройте трассировку на ui.perfetto.dev .
Выявите неисправности и замените.
Найдите группу Memory , затем разверните группу vmstat . Эта группа содержит различные счетчики уровня ядра, отслеживающие активность управления памятью во всей системе.

Представленные дорожки отображают различные аспекты состояния памяти ядра:
Счетчики состояния памяти (абсолютные значения) : Эти индикаторы показывают текущий объем памяти в определенном состоянии. На скриншоте они отображаются в виде абсолютных значений (например, в КБ или количестве страниц).
-
nr_free_pages: Объем физической оперативной памяти, которая полностью свободна. -
nr_active_anon/nr_inactive_anon: Анонимная память (подобная кучам и стекам), которая в данный момент используется (активна) или не использовалась некоторое время (неактивна). Ядро предпочитает сначала выгружать неактивные страницы в файл подкачки. -
nr_active_file/nr_inactive_file: Файловая память (кэш страниц), активная или неактивная. Неактивные страницы файлов являются первыми кандидатами на вытеснение.
-
Счетчики активности (частота) : Для счетчиков, отображающих общее количество событий за определенный период времени, таких как ошибки страниц и операции подкачки, часто полезнее просматривать частоту событий, а не их суммарное количество. В пользовательском интерфейсе Perfetto вы можете навести курсор на дорожку счетчика, щелкнуть значок метрики, затем щелкнуть «Режим» и выбрать между «Значение» , «Дельта » или «Частота» . Просмотр частоты значительно упрощает выявление всплесков активности и сопоставление их с другими системными событиями.
-
pswpout(Swap Out) : Скорость сжатия анонимных страниц и их перемещения в ZRAM . Высокие пики указывают на сильную нагрузку на память. -
pswpin(Swap In) : Скорость, с которой процессы считывают страницы, которые ранее были перемещены в ZRAM. -
pgfault(Total Page Faults) : Показатель количества ошибок страничного доступа, включая те, которые были обработаны без дискового ввода-вывода (незначительные ошибки). -
pgmajfault(Major Page Faults) : частота ошибок, для устранения которых потребовались операции ввода-вывода на диске (например, перезагрузка вытесненного кода из хранилища). Это ключевой индикатор неэффективного использования дискового пространства .
-
На скриншоте вы заметите, что когда nr_free_pages значительно снижается, мы видим соответствующие всплески в pswpout , поскольку ядро пытается освободить оперативную память. Позже, когда мы "прокачиваем" кэш страниц, мы видим всплески в pgmajfault и nr_active_file .
Обратите внимание на удаление страниц из кэша.
Чтобы найти события кэширования страниц в Perfetto: 1. В строке поиска введите mm_filemap_add_to_page_cache . 2. События будут отображаться в виде фрагментов в дорожках событий Ftrace (одна дорожка на каждый процессор). 3. Разверните процесс MemoryLab . Если ftrace настроен правильно, вы сможете увидеть активность filemap, коррелированную с потоками процесса.

-
mm_filemap_add_to_page_cache: Указывает на добавление страницы в кэш страниц. -
mm_filemap_delete_from_page_cache: Указывает на то, что страница была удалена из кэша. -
mm_filemap_fault: Указывает на ошибку страничного доступа, произошедшую в файле, отображенном в память.
Устранение ошибок путем перевода данных сначала в иноды, а затем в файлы.
Щелкните по отдельному событию add_to_page_cache . В панели «Подробности» найдите i_ino (номер inode). Это идентифицирует конкретный файл.

Вы можете вручную преобразовать inode в путь к файлу:
# Replace <INODE_NUMBER> with the value from Perfetto
adb shell find /system /data /apex /data/user/0 -inum <INODE_NUMBER>
Удобный скрипт: пакетное разрешение inode-узлов
Если у вас много событий, вы можете использовать запрос PerfettoSQL для извлечения всех уникальных inode из трассировки и их автоматического разрешения с помощью вспомогательного скрипта.
Извлечение инодов : используйте
trace_processorдля получения уникальных значенийi_ino:./trace_processor -Q "SELECT DISTINCT int_value FROM args t JOIN raw r ON r.arg_set_id = t.arg_set_id WHERE r.name = 'mm_filemap_add_to_page_cache' AND t.key = 'i_ino'" \ trace.perfetto-trace > inodes.txtРазрешение : Получите доступ к инодам непосредственно из терминала с помощью быстрого цикла командной оболочки:
while read -r inode; do echo "Inode $inode -> $(adb shell find /system /data /apex /data/user/0 \ -maxdepth 4 -inum "$inode" 2>/dev/null)" done < inodes.txt
Пример выходных данных с Pixel 10a:
Resolving 10 unique inodes for device localhost:27198...
Inode 14051 -> /data/user/0/com.android.memorylab/files/thrash_test.bin
Inode 1382 -> /system/framework/framework.jar
Inode 203 -> /system/bin/cmd
Inode 17998 -> /data/misc/logd/logcat
← Привязки сервиса | ↑ Вверх | Возврат →