Что на самом деле означает «использование памяти» в Windows — как правильно читать Working Set, Private Bytes, Commit и файл подкачки

· · Windows, Разработка Windows, Управление памятью, Working Set, Private Bytes, Commit, Файл подкачки, Мониторинг производительности, Диагностика, Sysinternals

Диспетчер задач показывает «Память» процесса как 1,2 ГБ. Но Process Explorer показывает Working Set 1,5 ГБ и Private Bytes 2,4 ГБ, а Size в VMMap ещё больше. Если смотреть систему целиком, читается «Committed 19.6/31.8GB».

Так сколько же гигабайт памяти это приложение в итоге использует?

Ответ в том, что какое число смотреть, зависит от того, что вы на самом деле хотите узнать. Показатель меняется в зависимости от того, хотите ли вы объём, сейчас резидентный в ОЗУ, объём, выделенный именно этому процессу, объём, который система обещала и дальше обеспечивать, или просто диапазон зарезервированных виртуальных адресов.

Путаница с показателями памяти Windows в том, что все они отображаются одним словом «память», хотя на деле измеряют следующие отдельные оси.

  • Сколько адресного пространства занято
  • Сколько commit потреблено
  • Резидентна ли страница сейчас в физической ОЗУ
  • Страница частная для процесса или совместно используемая
  • Сколько ещё выделения система в целом может поддержать

Статья рассчитана на тех, кто расследует рост памяти приложения или общесистемную нехватку памяти в Windows 10/11 и актуальном Windows Server, и собирает в одну картину связи между Working Set, Private Working Set, Private Bytes, Commit, Virtual Bytes, файлом подкачки, Available и ошибками страниц.

Процедура, как отличить, почему объекты .NET не собираются, подробно разобрана в «.NET: как отличить ожидание GC от утечки памяти», а конкретная работа с VMMap и Process Explorer — в «Process Explorer / Handle / VMMap на практике». Эта статья сосредоточена на общей предпосылке обоих: как читать числа со стороны ОС Windows.

1. Сначала вывод

  • Working Set — множество страниц, сейчас резидентных в ОЗУ. В него входят не только страницы, частные для процесса, но и страницы, которые можно разделить с другими процессами, например код DLL и файлы, отображённые в память.1
  • Private Working Set — та часть Working Set, которая сейчас принадлежит только этому процессу. Это удобное приближение «ОЗУ, которую этот процесс сейчас занимает один», но не полный объём, который выделило приложение.2
  • Private Bytes — объём commit, частный для этого процесса. Это отдельный показатель от того, резидентна ли память сейчас в ОЗУ. Поле PagefileUsage в структуре Win32 API на актуальной Windows тоже по сути представляет тот же Commit Charge, а не число байт, реально записанных в файл подкачки.2
  • «Committed X/Y» в диспетчере задач показывает X как текущий суммарный commit системы и Y как потолок commit. X — не использование файла подкачки. Y примерно определяется ОЗУ плюс файл подкачки.3
  • Reserve и Commit — разные вещи. Просто зарезервировать диапазон виртуальных адресов значит отложить его на будущее; это не потребляет тот же объём ни ОЗУ, ни потолка commit.45
  • Page Fault не обязательно означает дисковый ввод-вывод. Есть мягкие ошибки, которые разрешаются внутри ОЗУ, и жёсткие, которые читают из файла подкачки, исполняемых файлов, файлов, отображённых в память, и тому подобного.16
  • Утечку памяти судят не по одному снятию, а по тенденции при повторении одной и той же нагрузки. Особенно смотрите, продолжают ли Private Bytes и его разбивка ступенчато расти даже после окончания обработки, не возвращаясь к тому же установившемуся состоянию.

Одним предложением: Working Set — «объём, который сейчас в ОЗУ», Private Bytes — «объём, обещанный именно этому процессу», Commit — «объём, который обещала система в целом».

Выбор нужного показателя памяти WindowsКакой показатель смотреть, зависит от того, нужна резидентность в ОЗУ, частный commit процесса, общесистемный commit или диапазон виртуальных адресовобъём сейчас в ОЗУчастный обещанный объём процессаобщесистемный обещанный объёмзарезервированный диапазон адресовЧто вы хотите узнать об использовании памятиWorking SetPrivate BytesSystem CommitVirtual Bytes / ReservedРезидентность в физической ОЗУЧастный Commit процессаСравнение с Commit LimitВиртуальное адресное пространство

Рис. 1: Сначала разложите наблюдение «памяти много» на четыре отдельных вопроса.

2. Разложение «использования памяти» на четыре оси

Для начала думайте о памяти Windows не как о «одном столбике», а по четырём осям.

Четыре независимые оси классификации одной страницыОтдельно проверяйте состояние виртуального адреса, обеспечение закоммиченных страниц, резидентность в физической ОЗУ и возможность разделения с другими процессамиСмотрите одну страницу по четырём осямСостояние адресаFree / Reserved / CommittedОбеспечениеPage-file-backed / File-backedРезидентность в ОЗУResident / Not residentСовместностьPrivate / Shareable

Рис. 2: Даже у одной страницы состояние адреса, обеспечение, резидентность и совместность определяются независимо.

Mapped — не состояние адреса рядом с Free, Reserved и Committed, а категория области. Страницы в отображённом представлении тоже могут быть Committed. Так же Private — не носитель обеспечения, а классификация совместности. Поэтому обеспечение читайте как Page-file-backed или File-backed, а совместность — как Private или Shareable, отдельно.

Сочетание этих четырёх осей даёт следующее отношение представительных показателей.

Состояние страницы Working Set Private Working Set Private Bytes Семейство Virtual Bytes
Частная процессу, закоммичена, резидентна в ОЗУ Входит Входит Входит Входит
Частная процессу, закоммичена, не резидентна в ОЗУ Не входит Не входит Входит Входит
Общая страница DLL или отображённого файла, резидентна в ОЗУ Входит Обычно не входит Обычно не входит Входит
Reserved, но не закоммичена Не входит Не входит Не входит Может входить
Неиспользуемый диапазон адресов Не входит Не входит Не входит Обычно не входит
Соответствие типов страниц основным показателям памятиПоказывает, какие показатели включают резидентные частные страницы, нерезидентные частные, резидентные общие и только зарезервированные диапазоныPrivate, закоммичена, в ОЗУPrivate, закоммичена, не в ОЗУОбщая страница, в ОЗУReserved, не закоммиченаWorking SetPrivate Working SetPrivate BytesСемейство Virtual Bytes

Рис. 3: Working Set и Private Bytes считают разные множества страниц, поэтому между ними нет простого включения.

Важно здесь то, что Working Set и Private Bytes не находятся в простом отношении включения.

Private Bytes включает страницы, частные для процесса, но сейчас не резидентные в ОЗУ. Working Set, напротив, включает общие страницы — код DLL и разделяемую память — которые Private Bytes вообще не считает. Поэтому в зависимости от процесса и момента Working Set может быть больше Private Bytes или наоборот.

Кроме того, простое суммирование Working Set нескольких процессов может посчитать одну и ту же физическую страницу — например общую DLL — несколько раз. «Сумма Working Set каждого процесса равна используемой ОЗУ» не обязательно верно.

3. Виртуальное адресное пространство — Reserve и Commit это разное

3.1. Виртуальный адрес — не адрес физической ОЗУ

У каждого процесса своё частное виртуальное адресное пространство. Указатель, с которым работает приложение, не указывает напрямую на место в физической ОЗУ; Windows через таблицы страниц сопоставляет виртуальные адреса физическим страницам или данным на файле.7

Поэтому даже на ПК с 64 ГБ установленной ОЗУ виртуальное адресное пространство, которое может использовать данный 32-разрядный процесс, обычно намного меньше. И наоборот, нормально, что у 64-разрядного процесса виртуальное адресное пространство больше физической ОЗУ.

3.2. Reserved значит только «адрес застолблён»

MEM_RESERVE у VirtualAlloc резервирует непрерывный диапазон виртуальных адресов на будущее. На этом этапе с страницами не связано физическое хранилище, и диапазон нельзя читать или писать.45

Например, даже если база данных или среда выполнения резервирует диапазон адресов 8 ГБ на будущий рост, одно это не потребляет 8 ГБ ОЗУ и не 8 ГБ Private Bytes.

3.3. Committed — обещание «обеспечить, когда понадобится»

MEM_COMMIT переводит виртуальную страницу в состояние Committed и заставляет Windows обещать нужное обеспечение. Разрешены ли чтение, запись или исполнение, решает отдельно защита страницы — PAGE_READONLY, PAGE_READWRITE, PAGE_EXECUTE, PAGE_NOACCESS и так далее — поэтому само по себе Committed не значит «можно читать и писать». В момент commit это учитывается в системном Commit Charge, но фактическая физическая страница может не назначаться до первого обращения. Страница, к которой обратились впервые, обнуляется, проходит demand-zero fault и входит в Working Set.51

Поэтому, хотя и то и другое называют «выделением», на деле есть следующие три стадии.

Три стадии от Reserve через Commit к резидентности в ОЗУПоказывает поток резервирования виртуального адреса, коммита страницы и назначения физической страницы при первом обращении с входом в Working SetMEM_COMMITпервое обращение, demand-zero faultесли не обращалисьMEM_RESERVE - зарезервировать диапазонОтражается в семействе Virtual BytesCommitted - доступна по защите страницыОтражается в Private Bytes / System CommitНазначена физическая страница, в ОЗУОтражается в Working SetCommitted, но не резидентна

Рис. 4: Reserve, Commit и первое обращение — отдельные события, и каждое двигает свой показатель.

Эти три стадии отдельно двигают числа семейства Virtual Bytes, Private Bytes и Working Set соответственно.

3.4. Почему бывает OutOfMemory при свободной ОЗУ

Успех выделения памяти определяется не только свободной ОЗУ.

  • Процесс исчерпал своё виртуальное адресное пространство
  • Нет свободного диапазона адресов нужного размера, который был бы непрерывным
  • Общесистемный Commit Charge достиг Commit Limit
  • У Job Object, контейнера, среды выполнения или библиотеки свой предел
  • Это 32-разрядный процесс
  • Нативная куча фрагментирована

Даже на 64-разрядной Windows пользовательское виртуальное адресное пространство 32-разрядного процесса обычно 2 ГБ, если не задан IMAGE_FILE_LARGE_ADDRESS_AWARE. 32-разрядное приложение с этим флагом может использовать до 4 ГБ на 64-разрядной Windows.8

Поэтому «у ПК 20 ГБ свободной ОЗУ, а 32-разрядное приложение падает около 1,6 ГБ» — не противоречие. Это может быть вовсе не проблема ОЗУ, а фрагментация адресного пространства или жёсткий предел.

4. Working Set — страницы, которые сейчас в ОЗУ

Working Set — множество страниц в виртуальном адресном пространстве процесса, которые сейчас резидентны в физической ОЗУ.1

В этом множестве смешано следующее.

  • Собственные куча и стек процесса
  • Код EXE и DLL и данные только для чтения
  • Файлы, отображённые в память
  • Разделяемая память
  • Страницы, ставшие частными для процесса после копирования при записи
  • Страницы, к которым обращались среда выполнения и разные библиотеки

4.1. Рост Working Set не обязательно значит, что выделили больше

Первое обращение к уже закоммиченной странице может увеличить только Working Set, тогда как Private Bytes не меняется. Так же, когда большой файл отображают в память и читают последовательно, страницы с файловым обеспечением входят в Working Set, а Private Bytes почти не растёт.

Наоборот, когда Windows делает Trim Working Set в ответ на давление памяти, сжимается только Working Set, хотя приложение логически всё ещё держит ту же память. Повторное обращение возвращает её через Page Fault.

Поэтому падение Working Set не обязательно значит «приложение освободило», а рост — «приложение заново выделило».

Типичный поток, в котором растёт и падает только Working SetТа же закоммиченная страница входит в ОЗУ при первом обращении, становится нерезидентной при Trim и возвращается при повторном обращении, а Private Bytes всё это время учитываетсяпервое обращениеTrim при давлении памятиPage Fault при повторном обращенииТа же закоммиченная страницаРезидентна в ОЗУНе резидентнаВходит в Working SetНе входит в Working SetУчитывается в Private Bytes, пока закоммичена

Рис. 5: Working Set растёт и падает с резидентностью, но Private Bytes не уменьшается, пока commit той же страницы остаётся.

4.2. Working Set включает общие страницы

Если 10 процессов разделяют кодовые страницы одной DLL, эта страница может появиться в Working Set каждого процесса, хотя в физической ОЗУ существует только одна копия. Сумма Working Set, превышающая установленную ОЗУ, сама по себе ещё не признак беды.

Если хотите приблизиться к «ОЗУ, которую этот процесс сейчас занимает один», смотрите Private Working Set. Но и это не «вся память, которую процесс выделил» — строго частные страницы, сейчас резидентные.

4.3. Принудительное сжатие Working Set утечку не чинит

EmptyWorkingSet или SetProcessWorkingSetSize позволяют вытеснить страницы из Working Set процесса. Но это не операция, которая освобождает commit или снимает ссылки в куче. Видимое использование ОЗУ падает, Private Bytes не меняется, а следующее обращение может вызвать всплеск ошибок страниц.9

Если цифра в диспетчере задач уменьшается только сразу после кнопки «уменьшить память» и сразу возвращается, когда вы снова начинаете работать, это может быть просто Trim Working Set, а не настоящее «освобождение».

5. Private Bytes — объём commit, частный для процесса

Private Bytes — объём виртуальной памяти, закоммиченной исключительно для этого процесса. Это Commit Charge, который нельзя разделить с другим процессом, и неважно, резидентна ли она сейчас в ОЗУ. В PROCESS_MEMORY_COUNTERS_EX Microsoft этому значению соответствует PrivateUsage.102

В Win32 API есть ещё поле с запутывающим именем PagefileUsage, но актуальная документация определяет его как «Commit Charge этого процесса» и говорит, что это то же значение, что PrivateUsage. Иными словами, Private Bytes 2 ГБ не значит «в pagefile.sys записано 2 ГБ».2

На Private Bytes обычно влияют следующее.

  • Commit нативной кучи, которую используют HeapAlloc, malloc, new и подобные
  • Private Data, закоммиченные напрямую через VirtualAlloc
  • Закоммиченная область кучи GC .NET
  • Та часть стека потока, которая реально закоммичена
  • Commit Charge на всё представление, зарезервированное при отображении представления копирования при записи(FILE_MAP_COPY
  • Частные буферы, которые внутри держат библиотеки и SDK устройств

В представлении копирования при записи, созданном через FILE_MAP_COPY, каждая страница в итоге может стать частной, поэтому в момент отображения Windows резервирует Commit Charge, достаточный, чтобы обеспечить всё представление файлом подкачки. Из-за этого System Commit и Commit Charge процесса(Private Bytes)могут вырасти на размер всего представления ещё до того, как любая запись реально создаст частную копию.11

5.1. Почему Private Bytes не падает после free или GC

Даже когда память «освобождена» с точки зрения приложения, среда выполнения или аллокатор кучи может не сделать Decommit этой области обратно ОС, а оставить её для будущего повторного использования. Тогда Private Bytes остаётся высоким, хотя внутри приложения область уже пригодна к повторному использованию.

Он также может оставаться высоким, потому что жива только часть большой области, из-за фрагментации или потому что кэш или пул прогрелись до потолка.

Поэтому высокий Private Bytes сам по себе не доказывает утечку. Смотреть нужно сравнение во времени:

  1. Повторите одну и ту же обработку одно и то же число раз
  2. Подождите одно и то же время после обработки
  3. Проверьте, возвращается ли Private Bytes на тот же уровень или выходит на плато
  4. Через VMMap или дамп кучи проверьте, какая область или тип выросли
Почему Private Bytes не падает после free или GCPrivate Bytes меняется по-разному в зависимости от того, возвращает ли аллокатор ОС область, которая приложению больше не нужна, или держит её для повторного использованияDecommit / Releaseдержит для повторного использованияПриложение освобождает область через free / GCВозвращает ли аллокатор её ОСCommit Charge уменьшаетсяPrivate Bytes падаетОбласть остаётся закоммиченнойPrivate Bytes стоит высокоПулы, кэши, фрагментация

Рис. 6: То, что область стала пригодной к повторному использованию внутри приложения, не то же самое, что возврат её Commit ОС.

5.2. Сильный кандидатный рисунок утечки

Рост вроде следующего, где пол «ступеньками» поднимается с каждым раундом нагрузки, заслуживает внимания.

Private Bytes
  ^
  |                    ________
  |             ______|
  |      ______|
  |_____|
  +----------------------------> Повторения одной и той же обработки

Впрочем, даже лестница может быть несколькими раундами роста от первой JIT-компиляции, шрифтов, декодеров изображений, пулов соединений или прогрева кэша, после чего стабилизируется. Важно не то, что растёт, а то, что не сходится к установившемуся состоянию.

6. System Commit — что на самом деле такое «Committed X/Y»

Цифра «Committed X/Y» на вкладке [Производительность] → [Память] диспетчера задач — общесистемный показатель.

  • X: System Commit Charge — закоммиченная память, которую Windows сейчас обещает обеспечить по всей системе
  • Y: System Commit Limit — потолок commit, который система может поддержать

Commit Limit примерно определяется физической ОЗУ плюс сумма всех файлов подкачки. Без файла подкачки получается чуть меньше установленной ОЗУ.36

Связь System Commit Charge и Commit LimitЧастный, общий секционный и ядерный commit составляют текущее значение X, а физическая ОЗУ и файл подкачки поддерживают потолок YX не может превысить YPrivate Commit каждого процессаSystem Commit Charge - XCommit общих секций с обеспечением файлом подкачкиCommit ядраФизическая ОЗУSystem Commit Limit - YФайл подкачки

Рис. 7: X — текущий обещанный объём, Y — потолок, который может поддержать это обещание; это не отображение использования файла подкачки.

В System Commit Charge входит не только сумма Private Bytes каждого процесса, но и Commit общих секций с обеспечением файлом подкачки и Commit, который потребляет ядро. Поэтому одной суммы попроцессных Private Bytes недостаточно, чтобы полностью объяснить X.

6.1. Commit Charge — не использование файла подкачки

Возьмите систему с 16 ГБ ОЗУ, файлом подкачки 16 ГБ и Committed 20/31 ГБ.

Эти 20 ГБ не значат «в файл подкачки записано 20 ГБ». Это суммарный объём, для которого Windows обещает предоставить обеспечение ОЗУ или файлом подкачки, когда понадобится, для частных записываемых страниц и тому подобного.

В этот момент могут одновременно держаться такие состояния:

  • Большая часть резидентна в ОЗУ
  • Часть вытеснена в файл подкачки
  • Часть закоммичена, но к ней ещё не было первого обращения
  • Часть потреблена как commit на стороне ядра

Если хотите увидеть реальное использование файла подкачки, смотрите Paging File(*)\% Usage отдельно от Commit. Даже собственные материалы Microsoft объясняют, что высокое использование файла подкачки само по себе не обязательно указывает на проблему производительности и что судить нужно вместе с достижением Commit Limit, Modified Page List и фактическим вводом-выводом подкачки.6

6.2. Что происходит при приближении к Commit Limit

Когда System Commit Charge достигает Commit Limit, новые запросы commit обеспечить нельзя. Это ведёт к отказам выделения памяти процессов, падениям приложений и неотзывчивости системы.3

Здесь X/Y Commit важнее, чем «свободная ОЗУ». Даже если Trim Working Set освободит ОЗУ, достижение Commit Limit не снимается, пока сам Commit Charge не уменьшится.

6.3. Три роли файла подкачки

Файл подкачки в основном служит следующим ролям.

  1. Расширение Commit Limit
  2. Возможность вытеснить из ОЗУ редко используемые изменённые страницы
  3. Обеспечение системного дампа сбоя, в зависимости от конфигурации

Отключение файла подкачки — не простой случай «дисковый ввод-вывод всегда падает и всё становится быстрее». Скорее оно снижает Commit Limit, делает вероятнее, что изменённые, но сейчас ненужные страницы останутся в ОЗУ, и может сделать невозможным снять нужный дамп при сбое.36

Подходящий размер файла подкачки нельзя решить только по установленной ОЗУ. Microsoft сама объясняет, что обобщать нельзя, потому что пиковый System Commit Charge и нужный вид дампа сбоя различаются от системы к системе.6

7. Разбивка физической ОЗУ — не судите только по низкому Available

Физическая ОЗУ используется не только Working Set пользовательских процессов.

  • Working Set каждого процесса
  • Системный файловый кэш
  • Списки страниц вроде Standby, Modified, Free и Zeroed
  • Paged Pool / Nonpaged Pool ядра
  • Память, которую держат драйверы устройств
  • Хранилище сжатия памяти
  • Области, общие с GPU и другими устройствами или зарезервированные для них
  • Память, зарезервированная оборудованием

7.1. Available включает и пригодный к повторному использованию кэш

Available MBytes Windows — не просто полностью неиспользуемая ОЗУ. Это показатель, который наряду с Free и Zeroed включает страницы Standby, которые при необходимости можно использовать повторно.12

  • Free: страницы, сейчас не выделенные ни под какую цель
  • Zeroed: страницы, обнулённые так, что их можно безопасно отдать другому процессу
  • Standby: страницы, покинувшие Working Set, но чьё содержимое ещё кэшировано в ОЗУ
  • Modified: страницы, чьё содержимое изменилось и которые перед повторным использованием нужно записать в подходящее обеспечение
Движение между Working Set и списками страницПоказывает уход неизменённых страниц в Standby и изменённых в Modified, затем повторное обращение, запись и повторное использованиеубирают неизменённую страницуубирают изменённую страницузапись завершенаповторное обращениеповторно под другую цельобнулениеобращение после выделенияWorking Set - в использованииStandby - кандидат на повтор с сохранением содержимогоModified - ждёт записиВыделена под другую цельFree - не используетсяZeroed - доступна для нового выделенияВходит в Available

Рис. 8: Available включает не только полностью свободную память, но и Standby, которую при необходимости можно использовать повторно.

«Выбросить весь кэш, чтобы увеличить свободную ОЗУ» не всегда выигрыш. Если нужные данные ещё сидят в Standby, повторное обращение может быстро вернуть их в Working Set без чтения с диска.

Поэтому даже если Free в диспетчере задач низкий, если Available достаточно и жёсткие ошибки страниц или ожидание диска не создают проблем, Windows может просто эффективно использовать ОЗУ как кэш.

7.2. Когда ОЗУ сжимается без крупного процесса

Нередко потребление памяти остаётся необъяснённым даже после суммирования Private Working Set каждого процесса.

  • Файловый кэш и файлы, отображённые в память
  • Nonpaged Pool / Paged Pool
  • Страницы, заблокированные драйвером
  • Общие страницы
  • Сжатие памяти
  • Выделения, связанные с виртуализацией или GPU

В этом случае, вместо того чтобы продолжать смотреть на список процессов, проверьте Use Counts, Processes, Priority Summary и File Summary в RAMMap из Sysinternals. RAMMap — официальный инструмент, который раскладывает физическую память по назначению, списку страниц и файлу.13

Если растёт только Nonpaged Pool, это момент подозревать утечку на стороне драйвера или ядра, а не Private Bytes приложения в пользовательском режиме.

8. Page Fault — высокий счёт сам по себе не аномалия

Page Fault возникает, когда процесс обращается к странице, которой сейчас нет в его Working Set. Несмотря на слово «Fault» в имени, это не исключительный сбой — это обычный механизм, который двигает виртуальную память.1

8.1. Мягкие ошибки страниц

Разрешаются без чтения с диска.

  • Страница ещё в Standby или Transition
  • Та же общая страница уже есть в Working Set другого процесса
  • К закоммиченной странице обратились впервые и назначили нулевую страницу
  • Упреждающее чтение диспетчера памяти уже внесло её в ОЗУ

Поэтому большой \Memory\Page Faults/sec не обязательно значит, что идёт дисковый ввод-вывод или задержка.

8.2. Жёсткие ошибки страниц

Требуют чтения содержимого из хранилища обеспечения на диске. Источник не ограничен файлом подкачки.

  • Код и данные в .exe или .dll
  • Файл, отображённый в память
  • Файл подкачки
Ветвление мягких и жёстких ошибок страницПри обращении к странице вне Working Set это мягкая ошибка, если ввод-вывод хранилища не нужен, и жёсткая, если нуженНет - Standby, общая, demand-zero и т.д.ДаОбращение к странице вне Working SetНужен ли ввод-вывод хранилищаМягкая ошибка страницыВходит в Working Set без чтения дискаЖёсткая ошибка страницыОткуда читаетсяEXE / DLLФайл, отображённый в памятьФайл подкачкиВходит в Working Set после загрузки

Рис. 9: Одно имя «Page Fault» не скажет, был ли дисковый ввод-вывод.

Microsoft среди счётчиков для измерения жёстких ошибок перечисляет \Memory\Pages/sec, \Memory\Page Reads/sec и \Memory\Pages Input/sec. Поскольку их высота не обязательно значит нехватку памяти, соотносите их с Available MBytes, задержкой диска и фактическим временем отклика.6

8.3. Не ставьте один общий порог

Фиксированное значение вроде «всё выше 1000 Page Faults/sec аномально» меняет смысл в зависимости от хранилища, размера страницы, нагрузки и локальности доступа.

На практике выстройте следующее на одной временной оси.

  • Memory\Available MBytes
  • Memory\Pages Input/sec
  • Memory\Page Reads/sec
  • Задержка чтения / Queue целевого диска
  • Working Set и Private Bytes целевого процесса
  • Время обработки приложения, таймауты и отзывчивость интерфейса

Если Available падает одновременно с ростом нагрузки, Pages Input/sec и ожидание диска растут, и время обработки тоже ухудшается, это даёт основания подозревать подкачку из-за давления физической памяти.

9. Какой экран или инструмент смотреть для чего

Что хотите узнать Показатель, который смотреть первым Основные инструменты
Объём, который целевой процесс сейчас держит в ОЗУ Working Set Диспетчер задач, Process Explorer, Get-Process
Частную часть этого — ОЗУ, частную процессу Private Working Set / Working Set - Private Столбцы «Подробности» диспетчера задач, Process Explorer, PerfMon
Объём commit, частный целевому процессу Private Bytes / Commit Size Process Explorer, PerfMon, VMMap, Get-Process
Диапазон виртуальных адресов процесса Virtual Bytes / Size Process Explorer, VMMap, Get-Process
Общесистемный запас commit Committed Bytes / Commit Limit Диспетчер задач [Производительность], PerfMon
Запас повторного использования физической ОЗУ Available MBytes Диспетчер задач, PerfMon
Разбивку Standby, Modified и файлового кэша Список страниц / разбивка по назначению RAMMap
Что выросло внутри Private Bytes Heap / Private Data / Managed Heap и т. д. VMMap, WinDbg, дампы конкретной среды выполнения
Подкачку с участием диска Pages Input/sec, Page Reads/sec, задержка диска PerfMon, WPR/WPA
Выбор инструмента расследования памяти WindowsИнструмент зависит от того, цель — один процесс или вся система, одна точка во времени или ряд, и нужно ли проследить удержание внутри среды выполнениядаразбивка одной точкивременной рядвся системаразбивка физической ОЗУшкала с ЦП, вводом-выводом и ожиданиямикуча .NETнативная кучаЧто хотите изолироватьЦель — один процесс?Одна точка или временной рядVMMapPerfMon / PowerShellРазбивка физической ОЗУ или шкала времениRAMMapWPR / WPAНужно ли проследить удержание внутри средыdotnet-dump / PerfViewWinDbg / Application Verifier

Рис. 10: Сначала решив область и шкалу времени, вы выбираете ровно тот инструмент, который нужен, не больше и не меньше.

9.1. Диспетчер задач

В диспетчере задач смотрите экраны раздельно.

  • [Процессы] или [Подробности]: семейства Working Set и Commit Size отдельных процессов
  • [Производительность] → [Память]: общесистемные In use, Available, Committed, Cached, Paged pool, Non-paged pool

Не судите только по столбцу с именем «Память» — щёлкните правой кнопкой заголовки столбцов на вкладке [Подробности] и добавьте нужные столбцы, например Working Set, Peak Working Set и Commit Size. Имена столбцов немного различаются в зависимости от версии Windows и языка отображения, поэтому подтвердите, что столбец на самом деле означает, прежде чем записывать.

9.2. Снятие временного ряда через PowerShell

Если известен идентификатор целевого процесса, тенденции Working Set, Private Bytes и Virtual Bytes можно снять вместе через Get-Process.

param(
    [Parameter(Mandatory)]
    [int]$ProcessId,

    [int]$IntervalSeconds = 5,
    [int]$SampleCount = 60
)

$samples = for ($i = 0; $i -lt $SampleCount; $i++) {
    $process = Get-Process -Id $ProcessId -ErrorAction Stop

    [pscustomobject]@{
        Timestamp      = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
        ProcessId      = $process.Id
        WorkingSetMB   = [math]::Round($process.WorkingSet64 / 1MB, 1)
        PrivateBytesMB = [math]::Round($process.PrivateMemorySize64 / 1MB, 1)
        VirtualBytesMB = [math]::Round($process.VirtualMemorySize64 / 1MB, 1)
        Handles        = $process.HandleCount
        Threads        = $process.Threads.Count
    }

    Start-Sleep -Seconds $IntervalSeconds
}

$samples | Format-Table -AutoSize
$samples | Export-Csv .\memory-samples.csv -NoTypeInformation -Encoding utf8

Process.WorkingSet64 в .NET соответствует Working Set, PrivateMemorySize64 — Private Bytes, VirtualMemorySize64 — Virtual Bytes.141516

Для приложения с несколькими экземплярами отслеживайте по PID, а не по имени. Для длительного мониторинга, где перезапуск меняет PID, проектируйте сбор так, чтобы записывать время старта, имя службы и подобное, чтобы цель никогда не путалась.

9.3. Система и процесс на одной шкале времени через PerfMon

Запись как минимум следующего вместе сильно упрощает изоляцию.

\Process(<цель>)\ID Process
\Process(<цель>)\Working Set
\Process(<цель>)\Working Set - Private
\Process(<цель>)\Private Bytes
\Process(<цель>)\Virtual Bytes

\Memory\Available MBytes
\Memory\Committed Bytes
\Memory\Commit Limit
\Memory\Pages Input/sec
\Memory\Page Reads/sec
\Memory\Pool Nonpaged Bytes
\Memory\Pool Paged Bytes

Когда несколько процессов разделяют одно имя или во время мониторинга происходит перезапуск, одного имени экземпляра — Process(name) или Process(name#N) — недостаточно, чтобы зафиксировать цель. Записывайте ID Process для каждой выборки и принимайте только тот экземпляр, чьё значение совпадает с отслеживаемым PID. Когда охватываете перезапуск, меняющий PID, отдельно записывайте и время переключения.

Имена счётчиков производительности Windows могут локализоваться в зависимости от языка отображения. Если прямое указание английского имени в PowerShell не находит счётчик, добавьте его через графический интерфейс PerfMon или проверьте имена в локальной среде через Get-Counter -ListSet *.

9.4. Не путайте роли VMMap и RAMMap

  • VMMap: раскладывает виртуальную память и Working Set одного процесса на Heap, Image, Mapped File, Private Data, Managed Heap и подобное
  • RAMMap: раскладывает физическую ОЗУ всей системы по назначению, списку страниц, процессу и файлу

«Из-за чего выросли Private Bytes этого процесса» — работа VMMap; «на что ушла ОЗУ, которую не объясняет список процессов» — работа RAMMap.1713

10. Чтение симптомов по сочетаниям чисел

Наблюдаемый рисунок Первая гипотеза Что проверять дальше
Working Set растёт, Private Bytes стабилен Первое обращение к существующим страницам, общая DLL, отображённый файл, файловый кэш Image / Mapped File в VMMap, Pages Input/sec
Private Bytes растёт, Working Set стабилен Частный Commit вырос, но нерезидентен или Trim-нут Heap / Private Data / Managed Heap в VMMap
Оба растут сразу после старта, затем выравниваются JIT, кэш, пул, прогрев инициализации Растёт ли снова при той же дополнительной нагрузке
Пол Private Bytes поднимается с каждым раундом нагрузки Утечка, кэш без потолка или аллокатор, который держит память после освобождения Снимки VMMap до и после, дамп кучи
Только Working Set внезапно падает и возвращается с активностью ОС или приложение сделали Trim Working Set Private Bytes, Pages Input/sec, время отклика
X в Committed X/Y приближается к Y Общесистемное давление commit Крупнейшие потребители Private Bytes, Paged/Nonpaged Pool, настройки файла подкачки
Available низок, Pages Input/sec и задержка диска высоки Давление физической ОЗУ и жёсткая подкачка Крупнейшие потребители Working Set, RAMMap, корреляция нагрузки
Использование ОЗУ высоко, но крупного процесса нет Кэш, общие страницы, пулы ядра, драйверы, сжатие и т. д. RAMMap, Pool Nonpaged/Paged Bytes
Свободная ОЗУ есть, а падает только 32-разрядное приложение Потолок виртуального адресного пространства или фрагментация Free/Reserved в VMMap, настройка LAA исполняемого файла
Private Bytes высок, но не растёт при повторной обработке Пул или кэш, возможно держащий высокую водяную отметку Его предел, поведение повторного использования, стабильность после пика

Самое важное в этой таблице — читать её в сочетании, а не по одному значению.

11. Практическая процедура расследования утечки памяти

11.1. Сначала зафиксируйте условия воспроизведения и точку установившегося режима

Одного «растёт за несколько дней» недостаточно для сравнения.

  • Какую долю прогрева после старта включать
  • Из чего состоит один цикл работы
  • Сколько секунд ждать после одного цикла
  • Сколько раундов нужно, чтобы достичь потолка кэша
  • Можно ли использовать один и тот же ввод для исправной и проблемной сборок

Решите всё это.

11.2. Записывайте процесс и систему одновременно

Как минимум ведите следующий журнал с одними и теми же метками времени.

  • Working Set цели
  • Private Bytes цели
  • Virtual Bytes цели
  • Committed Bytes / Commit Limit системы
  • Available MBytes
  • Pages Input/sec
  • Число хендлов, число потоков
  • Число операций или обработанных элементов

Если Private Bytes процесса стабилен, а системный Commit продолжает расти, нужно расширить обзор на другие процессы, ядро, драйверы и общие секции.

11.3. Сначала решите, какое «измерение» растёт

  • Только Working Set: резидентные страницы, общие или файловые, Trim и повторная загрузка
  • Private Bytes: частный commit процесса
  • Только Virtual Bytes: Reserve, отображение, фрагментация адресного пространства
  • Только System Commit: включая другие процессы и сторону ядра
  • Nonpaged Pool: сторона драйвера/ядра
  • Handles / GDI / USER: утечки ресурсов помимо памяти

Пропустить этот порядок и сразу снимать дамп значит читать гору информации, целясь не туда.

11.4. Переходите к разбивке

  • Нативный процесс: VMMap, WinDbg, Application Verifier, трассировка кучи
  • .NET: dotnet-counters, dotnet-gcdump, dotnet-dump, PerfView
  • Вся система: RAMMap, PerfMon, WPR/WPA
  • Пул ядра: PoolMon, WinDbg

VMMap показывает закоммиченную виртуальную память процесса и Working Set, выделенный каждой её части, в разбивке по типу. Насколько удаётся сузить рост Private Bytes до Heap, Private Data, Managed Heap или Mapped File, сильно меняет стоимость дальнейшего расследования.17

11.5. После исправления сравнивайте тенденцию в тех же условиях

Недостаточно, чтобы пиковое значение отличалось до и после исправления. Если отличается стартовое значение, сравнение легко переворачивается.

  • Одно и то же состояние старта
  • Один и тот же ввод
  • Одно и то же число операций
  • Одно и то же время ожидания
  • Один и тот же интервал выборки

— и сравнивайте значение пола и тенденцию после каждого цикла. Доказательство исправления утечки — не «максимум стал меньше», а то, что рост теперь сходится, даже когда та же нагрузка повторяется.

12. Переформулировка распространённых заблуждений

Заблуждение 1: Память в диспетчере задач равна полному объёму, который выделило приложение

Переформулировка: Проверьте, какой это столбец. Для семейства Working Set это объём, сейчас резидентный в ОЗУ; для семейства Commit Size — commit, частный этому процессу.

Заблуждение 2: Private Bytes равны байтам в файле подкачки

Переформулировка: Private Bytes — частный Commit Charge. Это логический обещанный объём, который включает и страницы, сейчас в ОЗУ, и страницы, которые при необходимости обеспечит файл подкачки.

Заблуждение 3: Commit X/Y равен использованию / ёмкости файла подкачки

Переформулировка: X — общесистемный Commit Charge, Y — Commit Limit. Файл подкачки расширяет Y, но X не переводится напрямую в использование на диске.

Заблуждение 4: Высокий Page Faults/sec значит свопинг на диск

Переформулировка: Сюда входят и мягкие ошибки. Проверьте Pages Input/sec, Page Reads/sec и задержку диска, чтобы увидеть, участвует ли реально дисковый ввод-вывод.

Заблуждение 5: Низкая свободная ОЗУ значит нехватку памяти

Переформулировка: Смотрите Available, Standby, жёсткую подкачку и время отклика. Заполнять ОЗУ пригодным к повторному использованию кэшем нормально.

Заблуждение 6: Сжать Working Set значит починить утечку памяти

Переформулировка: Возможно, вы только вытеснили страницы из ОЗУ. Проверьте, уменьшились ли реально Private Bytes и то, что удерживается внутри кучи.

Заблуждение 7: Рост Private Bytes подтверждает утечку

Переформулировка: Судить можно только после проверки, сходится ли это при повторении одной и той же нагрузки, какой вид памяти вырос и не является ли это освобождаемым кэшем.

13. Итоги

  • «Использование памяти» Windows — не одно число. Думайте отдельно об адресном пространстве, commit, резидентности в ОЗУ и совместности.
  • Working Set — страницы, которые сейчас в ОЗУ, включая и Private, и Shared. Private Working Set — частные резидентные страницы процесса внутри этого.
  • Private Bytes — частный Commit Charge процесса; это ни объём, сейчас находящийся в ОЗУ, ни объём, реально записанный в файл подкачки.
  • Committed X/Y — общесистемные Commit Charge / Commit Limit. Файл подкачки в основном поддерживает Commit Limit, вытеснение изменённых страниц и дампы сбоя.
  • Зарезервированный виртуальный адрес, закоммиченная страница и страница, к которой реально обратились и которая вошла в Working Set, — отдельные стадии.
  • Page Fault — нормальная работа, и мягкая ошибка не читает с диска. Жёсткие ошибки тоже могут идти не только из файла подкачки, но из EXE, DLL или отображённого файла.
  • Утечка памяти доказывается не размером в одной точке времени, а значением пола и тенденцией после той же нагрузки вместе с разбивкой.
  • Базовый путь: VMMap для разбивки отдельного процесса, RAMMap для общесистемной физической ОЗУ, PerfMon для временного ряда и специализированный инструмент дампа для того, что происходит внутри среды выполнения.

В следующий раз, заметив в диспетчере задач, что «память растёт», начните с этого вопроса.

Растёт Working Set, Private Bytes, Virtual Bytes или System Commit?

Один этот вопрос делает точку входа в расследование заметно точнее.

Похожие статьи

Смежные области консультирования

KomuraSoft LLC проводит расследования первопричин, которые сочетают PerfMon, VMMap, RAMMap, WinDbg и диагностические инструменты .NET, для роста памяти Windows-приложений, деградации производительности после долгой работы, OutOfMemory в 32-разрядных процессах и нехватки памяти, которая возникает только в среде заказчика. Мы не останавливаемся на просто «памяти много» — изолируем, какая область выросла, через какую операцию, почему и откуда на неё ссылаются или её удерживают.

Справочные ссылки

  1. Microsoft Learn, Working Set. О том, что Working Set процесса — множество страниц, сейчас резидентных в физической памяти, включая общие страницы; о разнице между мягкими и жёсткими ошибками страниц; о страницах Transition; и об удалении страниц из Working Set.  2 3 4 5

  2. Microsoft Learn, PROCESS_MEMORY_COUNTERS_EX2 structure. Об определениях WorkingSetSize, PrivateWorkingSetSize, PrivateUsage и SharedCommitUsage и о том, что и PagefileUsage, и PrivateUsage представляют Commit Charge процесса.  2 3 4

  3. Microsoft Learn, Introduction to page files. О том, что файл подкачки поддерживает вытеснение изменённых страниц, системные дампы сбоя и расширение System Commit Limit; об определениях System Commit Charge и Commit Limit; и об измерении через диспетчер задач и счётчики производительности.  2 3 4

  4. Microsoft Learn, Page State. О состояниях Free, Reserved и Committed виртуальной страницы и о том, что со страницами Reserved не связано физическое хранилище и они недоступны.  2

  5. Microsoft Learn, VirtualAlloc function. О разнице между MEM_RESERVE и MEM_COMMIT; о том, что commit учитывается против общей памяти системы и файла подкачки; и о том, что фактическая физическая страница иногда не выделяется до первого обращения.  2 3

  6. Microsoft Learn, How to determine the appropriate page file size for 64-bit versions of Windows. О том, что размер файла подкачки зависит от пикового Commit Charge и требований к дампу сбоя; о том, что жёсткие ошибки страниц читают не только из файла подкачки, но и из EXE, DLL и файлов, отображённых в память; и о связанных счётчиках производительности.  2 3 4 5 6

  7. Microsoft Learn, Virtual Address Space. О том, что у каждого процесса своё независимое виртуальное адресное пространство и таблица страниц, и виртуальный адрес сам по себе не является физическим адресом. 

  8. Microsoft Learn, Memory Limits for Windows and Windows Server Releases. О том, что пользовательское виртуальное адресное пространство 32-разрядного процесса обычно 2 ГБ и на 64-разрядной Windows становится 2 ГБ или 4 ГБ в зависимости от IMAGE_FILE_LARGE_ADDRESS_AWARE. 

  9. Microsoft Learn, SetProcessWorkingSetSize function. О том, что минимум и максимум Working Set не гарантируют резидентность; о возможности опустошить Working Set; и о том, что чрезмерные настройки или операции могут ухудшить производительность системы. 

  10. Microsoft Learn, Memory Performance Information. О соответствии между счётчиками производительности Windows, API управления памятью и отображением диспетчера задач, включая Working Set / Working Set - Private / Private Bytes объекта Process и Committed Bytes / Commit Limit объекта System. 

  11. Microsoft Learn, MapViewOfFile function. О том, что FILE_MAP_COPY делает каждую страницу потенциально копируемой при записи, поэтому Commit Charge на всё представление резервируется для обеспечения файлом подкачки в момент отображения. 

  12. Microsoft Learn, Understanding Node Metrics and Properties in HPC Cluster Manager. О том, что Available Physical Memory считается как сумма списков Zeroed, Free и Standby, и о значении каждого из этих списков страниц. 

  13. Microsoft Sysinternals, RAMMap. Об анализе использования физической памяти Windows по назначению, списку страниц, процессу, приоритету, физической странице и файлу.  2

  14. Microsoft Learn, Process.WorkingSet64 Property. О том, что WorkingSet64 возвращает Working Set процесса в байтах, соответствуя счётчику производительности Working Set объекта Process. 

  15. Microsoft Learn, Process.PrivateMemorySize64 Property. О том, что PrivateMemorySize64 возвращает память, частную процессу и не разделяемую с другими процессами, соответствуя счётчику производительности Private Bytes. 

  16. Microsoft Learn, Process.VirtualMemorySize64 Property. О том, что VirtualMemorySize64 возвращает объём виртуальной памяти, выделенной процессу, соответствуя счётчику производительности Virtual Bytes. 

  17. Microsoft Sysinternals, VMMap. О разбивке закоммиченной виртуальной памяти процесса по типу и отображении физической памяти(Working Set), выделенной каждой части, вместе с подробной картой памяти.  2

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Показывает ли столбец «Память» в диспетчере задач всю память, которую выделило приложение?
Нет. В диспетчере задач несколько столбцов памяти — семейство Working Set, семейство Private Working Set, Commit Size и другие — и смысл зависит от того, какой экран и столбец вы смотрите. Working Set — страницы, которые сейчас резидентны в ОЗУ; Private Bytes или Commit Size — собственный объём commit этого процесса. Не читайте один столбец «Память» как полную ёмкость, которую выделило приложение, или как размер утечки.
В чём разница между Working Set и Private Bytes?
Working Set — объём страниц, видимых этому процессу и сейчас резидентных в физической ОЗУ, включая совместно используемые страницы вроде кода DLL и файлов, отображённых в память. Private Bytes — объём закоммиченной памяти, которую использует исключительно этот процесс, независимо от того, резидентна ли она сейчас в ОЗУ. Поэтому они никогда не равны, и ни один из них не бывает стабильно больше другого.
Означает ли «Committed 18/32GB» в диспетчере задач, что 18 ГБ записаны в файл подкачки?
Нет. Левая цифра — суммарный commit, который система в целом сейчас обещает обеспечить; правая — потолок commit, который система может поддержать. Потолок примерно определяется ОЗУ плюс файл подкачки, но не весь левый итог реально лежит в файле подкачки. Большинство закоммиченных страниц в ОЗУ, а некоторым закоммиченным страницам физическая страница ещё ни разу не назначалась. Страницы же, которые можно заново загрузить из исходного файла — EXE, DLL и отображённые в память файлы — не обязательно поднимают частный Commit на ту же величину, что Working Set.
Можно ли получить OutOfMemory, когда свободная ОЗУ ещё есть?
Да. Выделение может провалиться по причинам, не связанным с физической ОЗУ: исчерпание виртуального адресного пространства 32-разрядного процесса, нехватка непрерывного свободного диапазона адресов, системный потолок commit, либо пределы Job Object или среды выполнения. В частности, 32-разрядный процесс на 64-разрядной Windows обычно ограничен 2 ГБ пользовательского виртуального адресного пространства, если он не Large Address Aware.
Станет ли Windows быстрее, если отключить файл подкачки?
В общем случае этого нельзя предполагать. Отключение файла подкачки снижает системный потолок commit, затрудняет вытеснение неиспользуемых изменённых страниц из ОЗУ и влияет на настройку дампов при сбое. Размер файла подкачки нужно решать, измерив пиковый commit charge и нужный дамп сбоя — это не параметр, который отключают без обоснования.
Означает ли высокий Page Faults/sec нехватку памяти в системе?
По одному этому показателю не скажешь. Ошибки страниц включают мягкие, которые разрешаются страницами Standby в ОЗУ или страницами, общими с другим процессом, и жёсткие, которые читают с диска. Вместо изолированного Page Faults/sec смотрите Pages Input/sec, Page Reads/sec, Available MBytes, задержку диска и время обработки вместе на одной временной оси.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог