Поиск утечек и пожирателей памяти: smem, pmap, smaps

Рейтинг: 68.5% · 14 голосов
Подробный курс по диагностике и производительности Linux для админов и DevOps: с чего начать, методология USE, логи (journalctl, dmesg), процессы, CPU, память и OOM, диск и IO (iostat), сеть (ss, tcpdump), системные вызовы и strace, ltrace, perf, флеймграфы, ftrace, eBPF и bpftrace, lsof, мониторинг. Разбор каждого инструмента с чтением вывода.
Ответить
Аватара пользователя
Dmitry_SRE
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Поиск утечек и пожирателей памяти: smem, pmap, smaps

Сообщение Dmitry_SRE »

Оглавление курса (47)
  1. С чего начать диагностику Linux: методология вместо паники
  2. Первые 60 секунд: экспресс-диагностика нагруженного сервера
  3. Методологии диагностики: USE, RED и здравый смысл
  4. Логи systemd через journalctl: где искать причину
  5. Где лежат логи Linux: /var/log, dmesg и rsyslog
  6. Источники правды: load average, /proc и /sys
  7. Процессы Linux: ps, pstree и состояния процессов
  8. Интерактивный мониторинг: top и htop
  9. Метрики процессов во времени: pidstat
  10. Приоритеты и ограничения: nice, ionice, cgroups
  11. Сигналы и зависшие процессы: kill, и что делать с D-state
  12. Загрузка CPU: user, system, iowait, steal и контекст-свитчи
  13. Диагностика CPU по ядрам: vmstat и mpstat
  14. Профилирование CPU: perf top и поиск пожирателя
  15. Частоты, троттлинг и NUMA: turbostat и numastat
  16. Память Linux: RSS, VSZ, page cache и миф о нехватке памяти
  17. Сколько памяти занято: free, /proc/meminfo и vmstat
  18. Поиск утечек и пожирателей памяти: smem, pmap, smaps (вы здесь)
  19. Swap и подкачка: swapon, swappiness, когда своп - это боль
  20. OOM killer: кто и за что убил процесс
  21. Память ядра и slab: slabtop и куда уходит RAM
  22. Подсистема ввода-вывода: путь запроса от приложения до диска
  23. Диагностика диска: iostat и чтение await, %util
  24. Кто грузит диск: iotop и атрибуция io процессам
  25. Файловые системы: df, du, иноды и куда делось место
  26. Глубокая диагностика блочного слоя: blktrace и biolatency
  27. Кеш страниц и грязные данные: dirty pages, fsync, drop_caches
  28. Сетевой стек Linux: путь пакета и где возникают задержки
  29. Сокеты и соединения: ss и netstat
  30. Захват трафика: tcpdump для диагностики сети
  31. Задержки и потери в сети: ping, mtr, диагностика латентности
  32. Сеть как источник проблем: nftables, conntrack, дропы
  33. Что такое системный вызов и зачем его трассировать
  34. strace: трассировка системных вызовов на практике
  35. strace в бою: почему программа висит, падает или тормозит
  36. ltrace: трассировка вызовов библиотек
  37. Накладные расходы трассировки и безопасные альтернативы
  38. perf: универсальный профайлер Linux
  39. Флеймграфы: визуализация профиля производительности
  40. Трассировка ядра: ftrace и trace-cmd
  41. eBPF: революция в наблюдаемости Linux
  42. Инструменты eBPF на практике: BCC и bpftrace
  43. lsof: открытые файлы, дескрипторы, кто держит файл и порт
  44. Разбор кейса: приложение тормозит - пошаговая диагностика
  45. Непрерывный мониторинг: sar, node_exporter, Grafana, Zabbix
  46. Диагностика в контейнерах: cgroups, лимиты, Docker и Kubernetes
  47. Карта инструментов и сквозной разбор инцидента производительности
Знакомая картина: на сервере кончается RAM, система начинает свопиться, прилетает OOM-killer и убивает что-нибудь важное. Ты открываешь top, видишь десяток процессов и ловишь себя на мысли - а кто из них реально виноват? Кто незаметно растет час за часом, а кто просто честно делает свою работу? В этом уроке разберемся, как найти настоящих пожирателей памяти и как отличить нормальную работу от утечки. Без отладчика, без перекомпиляции - только штатные инструменты, которые есть или ставятся в пару команд. А в конце добавим современный слой 2026 года: eBPF-трассировку утечки до конкретного стека вызовов.

Тема, на которой спотыкаются почти все: linux память процесса считается совсем не так наивно, как кажется. Цифра RSS в top врет, и сейчас ты поймешь почему.

Почему RSS врет и что вообще такое память процесса в linux

Когда ты смотришь на колонку RES в top или RSS в ps, ты видишь Resident Set Size - сколько физической RAM сейчас занимает процесс. Звучит честно, но есть подвох: сюда входит память, которую процесс делит с другими.

Представь библиотеку libc. Ее загрузили в RAM один раз, а пользуются ей сотни процессов. Но RSS каждого из них включает полный размер этих общих (shared) страниц. Если у тебя сотня процессов nginx или php-fpm и ты сложишь их RSS, получится цифра больше, чем вся RAM в системе. Это не магия - просто общую память посчитали много раз.

Поэтому для поиска пожирателей вводят более честные метрики:
  • RSS - вся резидентная память, включая общие страницы целиком. Завышает.
  • USS (Unique Set Size) - только то, что уникально процессу. Ровно столько RAM освободится, если процесс убить. Технически это Private_Clean + Private_Dirty из smaps. Занижает (игнорирует общее).
  • PSS (Proportional Set Size) - золотая середина. Уникальная память плюс "справедливая доля" общей: если страницу делят 4 процесса, каждому засчитывается четверть. Сумма PSS всех процессов как раз сходится с реально занятой RAM.
Запомни как мантру: чтобы понять, кто сколько реально съел - смотри PSS, а не RSS. На 2026 это не поменялось: ядро по-прежнему отдает PSS через /proc, а PSS остается единственной метрикой, которую можно складывать без двойного учета.

Изображение

smem: честный PSS по процессам - первый инструмент против утечки памяти linux

Утилита smem читает /proc/PID/smaps по всем процессам и считает PSS и USS. В большинстве дистрибутивов ставится так:

Код: Выделить всё

sudo apt install smem      # Debian/Ubuntu/Astra Linux
sudo dnf install smem      # Fedora/RHEL/RED OS
Базовый запуск - сортировка по PSS, чтобы худший был внизу:

Код: Выделить всё

$ sudo smem -t -k -s pss -r
  PID User     Command                         Swap      USS      PSS      RSS
 2841 www-data php-fpm: pool www              0          312.4M   318.1M   402.7M
 1190 mysql    /usr/sbin/mariadbd             4.0M       180.2M   181.0M   206.3M
 1503 root     /usr/bin/dockerd               0          88.1M    91.4M    140.9M
-------------------------------------------------------------------------------
   47 1                                       6.1M       640.5M   661.7M   1.1G
Разбираем по колонкам:
  • USS - сколько освободится при kill. У php-fpm 312M уникальны, при перезапуске вернутся в систему.
  • PSS - честная доля. 318M против RSS 402M: разница в 84M это та самая общая память (тот же libc, тот же opcache, разделяемый между воркерами).
  • RSS - завышенная цифра, которую ты привык видеть в top.
  • Swap - сколько ушло в своп. У mariadbd 4M в свопе - не криминал сам по себе, но звоночек, что памяти было тесно.
  • Строка -t (total) внизу: сумма PSS 661M это и есть реально занятое процессами. RSS суммарно 1.1G - вот насколько обманчива простая сумма RSS.
Флаги: -k показывает размеры в человеческих K/M/G, -s pss сортирует по PSS, -r переворачивает (самый жирный внизу, удобно в терминале). Полезные срезы: smem -u - агрегат по пользователям, smem -m - по библиотекам/маппингам (увидишь, какая разделяемая либа реально дорогая), smem -P php-fpm - фильтр по имени процесса. Когда нашли подозреваемого с растущим PSS, переходим к его вскрытию.

Если smem ставить нельзя (минимальный образ, нет интернета) - тот же PSS суммарно по процессу можно взять без него, прямо из ядра, об этом ниже в smaps_rollup.

pmap и smaps: вскрываем процесс и локализуем, куда утекает память

Нашли PID-обжору. Теперь надо понять, куда именно ушла память: в кучу (heap), в файлы через mmap, в библиотеки. Здесь главный инструмент - pmap.

Код: Выделить всё

$ sudo pmap -x 2841
2841:   php-fpm: pool www
Address           Kbytes     RSS   Dirty Mode  Mapping
000055b3c0a00000   18432   17200   17200 rw---   [ anon ]
00007f2a14000000  131072  128640  128640 rw---   [ anon ]
00007f2a3c1f0000    2048    1980       0 r-x--  libc.so.6
00007f2a3c400000     512     180       0 r----  locale-archive
...
---------------- ------- ------- -------
total kB         412800  402700  302100
Как читать (флаг -x дает расширенный вид с колонками RSS и Dirty):
  • Address - начальный адрес региона памяти.
  • Kbytes - размер виртуального региона (зарезервировано, это вклад в VSZ).
  • RSS - сколько из региона реально лежит в RAM.
  • Dirty - "грязные" страницы: изменены и не сброшены на диск. Это ключевая колонка для утечек. Большой anon-регион с огромным Dirty - это разросшаяся куча приложения, классическое место утечки. Чистые (clean) страницы кода ядро может выкинуть и перечитать с диска, грязные - нет, они держат RAM мертвой хваткой.
  • Mode - права: rw--- (данные/куча, изменяемое), r-x-- (исполняемый код библиотек), r---- (только чтение, например locale-archive).
  • Mapping - что за регионом: [ anon ] это анонимная память (куча, malloc, mmap без файла), [ stack ] стек, а имя файла - это mmap файла или библиотека.
В примере виновник очевиден: два rw-региона [ anon ] на 17M и 128M с почти полным Dirty - это куча php-fpm раздулась. Память не в библиотеках и не в кеше файлов, а именно в heap приложения. Это и есть локализация без отладчика: ты уже знаешь, что копать надо в логике самого приложения (бесконечно растущий массив, незакрытые объекты, кеш без лимита, утечка в C-расширении).

Под капотом pmap и smem читают /proc/PID/smaps - там по каждому региону десятки полей. Читать его построчно руками тяжело, поэтому с ядра 4.14+ есть агрегат smaps_rollup, который суммирует все регионы в одну сводку:

Код: Выделить всё

$ sudo cat /proc/2841/smaps_rollup
55b3c0a00000-7ffffffff000 ---p 00000000 00:00 0           [rollup]
Rss:              402700 kB
Pss:              318100 kB
Shared_Clean:      62000 kB
Shared_Dirty:          0 kB
Private_Clean:       600 kB
Private_Dirty:    301500 kB
Pss_Dirty:        300800 kB
Referenced:       350000 kB
Anonymous:        298000 kB
Swap:                  0 kB
Поля, которые реально важны для охоты на утечку:
  • Pss - та самая честная цифра по всему процессу, без установки smem.
  • Private_Dirty - приватные измененные страницы, которые никуда не денутся и ни с кем не делятся. Главный кандидат на утечку.
  • Pss_Dirty (есть с ядра 5.18+, актуально на 2026) - доля грязного в PSS. Удобно: показывает, сколько честной памяти процесса именно изменяемое, а не код-страницы.
  • Anonymous - память без файла за спиной (куча и стек). Это то, что нельзя сбросить на диск и перечитать.
  • Referenced - сколько страниц процесс трогал недавно (горячий рабочий набор). Если Rss большой, а Referenced заметно меньше - часть памяти холодная, кандидат на своп.
  • USS = Private_Clean + Private_Dirty - тут это примерно 302M.
Если Private_Dirty и Anonymous пухнут со временем у одного процесса - это сильный признак утечки в самом приложении, а не file cache.

Память процесса в динамике: ловим монотонный рост

Одна цифра ничего не доказывает. Утечка памяти linux - это не "много", а "монотонно растет и не падает". Процесс может честно занять 2G под кеш и стоять ровно - это не утечка. А может по 5M в минуту ползти вверх без остановки - вот это оно.

Снимай динамику через pidstat -r (пакет sysstat) - раз в 2 секунды, 5 раз:

Код: Выделить всё

$ pidstat -r -p 2841 2 5
12:00:01  UID  PID  minflt/s  majflt/s     VSZ     RSS  %MEM  Command
12:00:03 33   2841    420.00      0.00  812000  402700  10.1  php-fpm
12:00:05 33   2841    455.00      0.00  814000  408900  10.2  php-fpm
12:00:07 33   2841    480.00      2.00  816000  415100  10.4  php-fpm
Колонки:
  • minflt/s - малые page fault в секунду: страница нашлась в RAM (или подцепилась из page cache без чтения с диска), грузить с диска не пришлось. Нормальный фон, особенно у активного процесса.
  • majflt/s - большие page fault: пришлось читать страницу с диска. Если majflt/s стабильно ненулевой и растет - процессу тесно, он свопится или дергает mmap-файлы, это сигнал давления на память.
  • VSZ - виртуальный размер (Кбайт), сколько зарезервировано. Часто большой и не страшный сам по себе.
  • RSS - резидентная память (Кбайт). Вот тут смотри тренд: 402700 -> 408900 -> 415100, плюс 6M за 4 секунды и не откатывается. Это и есть монотонный рост.
Видишь стабильно ползущий вверх RSS без отката - почти наверняка утечка. Подтверди в большом окне: оставь pidstat -r -p PID 60 на час и сравни начало с концом. На 2026 системах вместо ручного наблюдения часто берут PSI - /proc/pressure/memory: если поле some avg10 устойчиво ненулевое, значит процессы реально тормозят из-за нехватки памяти, а не из-за безобидного кеша. А под cgroup v2 (дефолт в современных дистрибутивах) у каждого сервиса есть свой memory.current и memory.peak - systemctl status сервиса сразу покажет Memory: с пиком, и это самый быстрый способ заметить, что юнит распух.

eBPF: трассируем утечку до стека вызовов (актуально на 2026)

smem и pmap отвечают "куда" (в какой регион), но не "кто" (какая строка кода аллоцировала и не освободила). Раньше для этого лезли в valgrind с дикими тормозами или в gdb. На 2026 зрелый ответ - eBPF: инструмент memleak из пакета bcc (bpfcc-tools). Он цепляется к malloc/calloc/realloc/free через uprobes, собирает стек каждой аллокации и показывает те стеки, чья память так и не была освобождена - прямо на живом проде, без перекомпиляции и почти без остановки сервиса.

Код: Выделить всё

sudo apt install bpfcc-tools linux-headers-$(uname -r)   # Debian/Ubuntu/Astra
sudo dnf install bcc-tools                                # RHEL/RED OS

# трассируем процесс 2841, сводка outstanding-аллокаций каждые 5 сек
$ sudo memleak-bpfcc -p 2841 5
[12:30:05] Top 10 stacks with outstanding allocations:
        81920 bytes in 20 allocations from stack
                _emalloc+0x1a [php-fpm]
                cache_put+0x44 [app.so]
                handle_request+0x91 [app.so]
Как читать: показанные стеки - это память, которую запросили и НЕ вернули к моменту замера. Если из замера в замер один и тот же стек растет (было 81920, через минуту 240000, через две 600000) - вот он, конкретный путь утечки с именами функций. Полезные ключи: -a покажет адреса и размеры аллокаций, -T 5 ограничит вывод топ-5 стеками, -O путь_к_аллокатору нужен, если приложение использует свой аллокатор (jemalloc/tcmalloc) вместо glibc. Важная оговорка: чтобы видеть осмысленные имена функций, нужны символы (debug-символы или несорванный бинарь), а при экстремальном темпе аллокаций memleak дает заметный оверхед. Для managed-рантаймов (JVM, Go, PHP с Zend MM) бери профайлер самого рантайма - там своя куча поверх malloc. Точечную свою пробу можно написать и на bpftrace, но memleak закрывает 90% задач из коробки.

Где eBPF уже вытеснил старое (на 2026): для разовой диагностики аллокаций memleak практичнее valgrind massif, а связка smem (где) -> memleak (кто) заменяет долгие сессии в отладчике.

Типичные грабли и заблуждения
  • Складывать RSS всех процессов. Получишь больше физической RAM и решишь, что "память кончилась из-за процессов". Считай PSS через smem -t.
  • Путать file cache с утечкой. В free/htop "занятая" память часто это кеш файлов, который ядро мгновенно отдаст под нагрузку. Это buff/cache, не утечка. Смотри строку available в free -h: вот сколько реально можно занять без свопа. Утечка - это растущий Private_Dirty/Anonymous конкретного процесса.
  • Большой VSZ это плохо. Нет. VSZ это виртуальный резерв, его можно нарезать гигабайтами и не трогать RAM (так делают JVM, Go-рантайм, аллокаторы с аренами). Смотри RSS и PSS.
  • Один замер = диагноз. Память дышит. Утечка доказывается только трендом во времени, а не одним снимком.
  • Винить swap. Немного свопа при наличии free RAM - это нормальная работа ядра, оно вытесняет холодные страницы. Тревога - это активный своппинг (si/so в vmstat ненулевые постоянно) плюс растущий majflt/s.
Мини-лаба: повтори руками прямо сейчас
  • Поставь smem и запусти sudo smem -t -k -s pss -r. Найди свой топ-3 по PSS. Сравни PSS и RSS у самого жирного - оцени, сколько у него общей памяти.
  • Возьми PID браузера или сервера БД и выполни sudo pmap -x PID. Найди самый большой регион [ anon ] с большим Dirty - это его куча.
  • Сравни sudo cat /proc/PID/smaps_rollup: посмотри Pss, Private_Dirty, Pss_Dirty, Anonymous. Прикинь USS = Private_Clean + Private_Dirty.
  • Запусти pidstat -r -p PID 2 10 и последи за колонкой RSS - растет, падает или стоит ровно? Заодно глянь majflt/s.
  • Если стоит bpfcc-tools - запусти sudo memleak-bpfcc -p PID 5 на пару минут и посмотри, есть ли стек, который растет от замера к замеру.
Контрольные вопросы
  • Почему сумма RSS всех процессов может превысить объем физической RAM и какая метрика считает честно?
  • Чем USS отличается от PSS, из каких полей smaps складывается USS и в каком случае какую метрику брать?
  • В выводе pmap -x какая колонка и какое значение Mapping укажут на разросшуюся кучу приложения?
  • Какой признак в динамике pidstat -r отличает реальную утечку от процесса, который просто занял память под кеш, и чем тут поможет memleak?
Что запомнить

RSS врет из-за общих страниц - для честной картины бери PSS (smem -t или /proc/PID/smaps_rollup). Куда утекло, покажет pmap -x (ищи большие [ anon ] с Dirty) и smaps_rollup (Private_Dirty, Pss_Dirty, Anonymous). Сам факт утечки доказывается только динамикой: pidstat -r и монотонно растущий RSS, который не откатывается, плюс PSI и memory.current под cgroup v2. А чтобы выйти на конкретный стек кода - на 2026 это memleak из bcc прямо на проде. Кеш файлов утечкой не считается - его ядро отдаст под нагрузку, смотри available в free. Этого набора хватает, чтобы локализовать пожирателя памяти без отладчика и понять, в какую сторону копать дальше.
👍3 ❤️1 🔥2 😄 🤔
Аватара пользователя
vladigos
Сообщения: 1
Зарегистрирован: 12 май 2026, 04:26

Re: Поиск утечек и пожирателей памяти: smem, pmap, smaps

Сообщение vladigos »

Спасибо, наконец дошло почему top показывал суммарно больше памяти чем есть в системе. Поставил smem, у меня php-fpm воркеры по PSS в разы скромнее чем по RSS выглядят.
👍 ❤️1 🔥1 😄 🤔
Аватара пользователя
sss2000
Сообщения: 1
Зарегистрирован: 17 май 2026, 13:51

Re: Поиск утечек и пожирателей памяти: smem, pmap, smaps

Сообщение sss2000 »

memleak просто топ. Сел на прод, за 3 минуты увидел стек с cache_put который рос от замера к замеру - оказался кеш без лимита в нашем расширении. До этого неделю в gdb мучился.
👍 ❤️ 🔥1 😄 🤔
Ответить
← Предыдущая глава
Сколько памяти занято: free, /proc/meminfo и vmstat
Следующая глава →
Swap и подкачка: swapon, swappiness, когда своп - это боль

Все главы курса «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Поделиться темой: ✈ Telegram VK
Похожие запросы: как понять что съедает память на сервере linuxgit bisect: найти коммит с багом

Вернуться в «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость