Преодолевая ограничения Windows: виртуальная память

В моей первой публикации из серии "Преодолевая ограничения Windows" я рассказал об ограничениях, накладываемых на физическую память, включая ограничения, связанные с лицензированием, реализацией и совместимостью драйверов. В этот раз я решил обратить ваше внимание на другой фундаментальный системный ресурс - виртуальную память. Виртуальная память призвана в некотором смысле отвлечь внимание приложения от физической памяти, при этом операционная система должна решить, когда сохранять код и иную информацию в физической памяти (и стоит ли это делать вообще) и когда стоит сохранить их в файл. Главное преимущество виртуальной памяти заключается в том, что она позволяет одновременно выполняться большому количеству процессов, которые все сразу не смогли бы поместиться в физической памяти.

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

Адресное пространство процессов
У каждого процесса есть своя собственная виртуальная память, именуемая адресным пространством, в которой исполняется код этот процесса и его данные, на которые этот код ссылается и которыми управляет. 32-битные процессы используют 32-битные указатели на адреса в виртуальной памяти, которые создают абсолютный верхний предел в 4 ГБ (2 в 32-ой степени) на объем виртуальной памяти, которую 32-битный процесс может адресовать. Однако, чтобы операционная система могла обратиться к своему собственному коду и данным и к коду и данным, выполняющегося в настоящее время процесса, без необходимости изменять адресное пространство, она делает свою виртуальную память видимой из адресных пространств всех процессов. По умолчанию 32-битная версия Windows разделяет адресное пространство процесса поровну между системой и активным процессом, создавая границу в 2 Гб для каждого:

vm1

Приложения могли бы использовать для распределения виртуальной памяти Heap API, сборщик мусора .Net или Malloc-библиотеку C, но все они так или иначе опираются на VirtualAlloc API. Когда приложение исчерпывает свое адресное пространство, VirtualAlloc, а, следовательно, и диспетчеры памяти, построенные на его основе, возвращают ошибки (представленные адресом NULL). Утилита Testlimit, которую я написал для четвертого издания Windows Internals для того, чтобы продемонстрировать различные ограничения Windows, многократно вызывает VirtualAlloc до тех пор, пока не получит ошибку, вызванную указанием параметра -r. Таким образом, когда вы запускаете 32-битную версию Testlimit на 32-битной Windows, она займет все 2 Гб своего адресного пространства:

vm2

2010 Мб - это чуть меньше 2-х гигабайт, но наличие в памяти кодов и данных Testlimit, включая исполнительные и системные DLL, объясняют эту разницу. В графе Virtual Size утилиты Process Explorer вы можете увидеть общий объем адресного пространства программы:

vm3

Некоторые приложения, такие как SQL Server и Active Directory, управляют большими структурами данных, объем которых намного превышает доступное для них адресное пространство. Поэтому в Windows NT 4 SP3 ввели загрузочную опцию /3GB, позволяющую предоставить процессу 3 Гб из его четырех гигабайтного адресного пространства, уменьшая системное адресной пространство до 1 Гб, а в Windows XP и Windows Server 2003 встроили функцию /userva, которая перемещает границу разделения в пределах с 2 до 3 Гб:

vm4

Однако, чтобы использовать адресное пространство выше отметки 2 Гб, в исполняемом образе приложения обязательно должен содержаться набор флагов "large address space aware". Доступ к дополнительной виртуальной памяти является опциональным, потому что некоторые приложения рассчитаны на то, что им будет предоставлено не более 2 Гб адресного пространства. Так как старший разряд указателя, ссылающегося на адрес ниже отметки 2 Гб, всегда является нулем, они использовали бы его как флаг для их собственных данных, очищая его прежде, чем ссылаться на данные. Если бы такие приложения работали в 3-х гигабайтном адресном пространстве, они бы отсекли указатели, значения которых находятся выше 2 Гб, вызывая тем самым ошибки, в том числе и возможное нарушение целостности данных.

Все серверные продукты Microsoft и приложения Windows, интенсивно использующие память, имеют поддержку специального флага, позволяющего использовать адресное пространство выше отметки 2 ГБ; среди таких приложений Chkdsk.exe, Lsass.exe (которая являются главными сервисами Active Directory на контроллере домена), Smss.exe (менеджер сессий) и Esentutl.exe (инструмент восстановления базы данных Jet Active Directory). При помощи утилиты Dumpbin, входящей в состав Visual Studio, вы можете увидеть, у образов каких приложений есть этот флаг:

vm5

Testlimit также имеет этот флаг, так что если вы запустите его с параметром -r в 3-х гигабайтном пользовательском адресном пространстве, вы увидите что-то наподобие этого:

vm6

Поскольку адресное пространство в 64-битных Windows намного больше 4 Гб, такие версии Windows могут дать 32-битным процессам максимальные 4 Гб, которые они могут адресовать, и использовать оставшуюся виртуальную память для нужд операционной системы. Если вы запустите Testlimit на 64-битной Windows, то увидите, что она занимает все адресное пространство, к которому могут обратиться 32-разрядные приложения:

vm7

64-битные процессы используют 64-битные указатели, так что их теоретическое максимальное адресное пространство равно 16 экзабайтам (2 в 64-ой степени). Однако, Windows не делит адресное пространство равномерно между активными процессами и системой, а вместо этого определяет область в адресном пространстве для процессов и других системных ресурсов памяти, таких как системные записи таблицы страниц (PTE), файловые кэши, резидентный и нерезидентный (paged и non-paged) пулы.

Размеры адресного пространства процессов отличаются в IA64 и x64-версиях Windows, где он определяется так, чтобы соблюдался баланс между тем, сколько памяти требуется приложению, и издержками верхней памяти (страницы таблицы страниц и записи буфера трансляции адресов TLB), необходимых для поддержания адресного пространства. Для x64 это 8192 Гб (8 Тб), а для IA64 - 7168 Гб (7Тб) (разница в 1 Тб объясняется тем, что каталоги страниц верхнего уровня в IA64 резервируют слоты для распределений Wow64). И в IA64- и в x64-версиях Windows размер областей адресного пространства для различных ресурсов составляет 128 Гб (например, для нерезидентного пула отводится 128 Гб адресного пространства), за исключением кэша файла, которому выделяется 1 Тб. Поэтому адресное пространство 64-битного процесса выглядит примерно вот так:

 

 

 

 

 

vm8

На этом рисунке не соблюден масштаб, поскольку в ином случае даже 8 Тб, не говоря уже о 128 Гб, выглядели бы на нем тоненькой полоской. Я использовал этот рисунок, чтобы показать вам, что, как и в случае с нашей вселенной, в адресном пространстве 64-битного процесса есть много незанятного места.

Когда вы запустите 64-битную версию Testlimit (Testlimit64) на 64-битной Windows с параметром -r, вы увидите, что она занимает 8 Тб, которые являются той частью адресного пространства, которыми она может управлять:

vm9

vm10

Выделенная память
Параметр -r в утилите Testlimit резервирует виртуальную память, но не выделяет ее. Зарезервированная виртуальная память не может хранить данные или код, но приложения иногда используют резервирование для создания больших блоков виртуальной памяти, которую впоследствии они могут выделять, когда необходимо, чтобы выделенная память находилась в смежной с адресным пространством области. Когда процесс выделяет область в виртуальной памяти, операционная система гарантирует, что может предоставить для размещения всех данных процесса область или в физической памяти, или на диске. Это означает, что процесс может столкнуться с еще одним видом ограничений: ограничением на объем выделяемой памяти.

Из вышеизложенного следует, что граница у объема выделяемой памяти равна сумме физической памяти и размера файла подкачки. В действительности в определении границы учитывается не вся физическая память, потому как ее часть операционная система резервирует для своих нужд. Объем выделенной виртуальной памяти для всех активных процессов, называемый текущей выделенной памятью (current commit charge), не может превысить предела, установленного системой. Когда этот предел достигнут, службы, занимающиеся выделением виртуальной памяти, выдают ошибку. Это означает, что даже стандартный 32-битный процесс может столкнуться с ошибкой выделения виртуальной памяти еще до того, как он достигнет предела на объем виртуального адресного пространства в 2 Гб.

Текущий объем выделенной памяти и ее ограничение можно посмотреть в окне System Information утилиты Process Explorer в разделе Commit Charge и на графике Commit History:

 

 

vm11

Диспетчер задач в системах до Vista и Windows Server 2008 точно показывает эти параметры, но там текущий объем выделенной памяти на графике назван "PF Usage":

vm12

В Vista и Server 2008 диспетчер задач не показывает график текущей выделенной памяти, а отображает эти два значения в строке Page File (даже если вы отключите файл подкачки, эти значения будут ненулевыми):

vm13

Чтобы проверить значение границы выделенной памяти, вы можете запустить Testlimit с параметром -m, чтобы программа распределила выделенную память. 32-битная версия Testlimit может и не достигнуть предела своего адресного пространства до того, как достигнет предела на выделенную память; это зависит от объема физической памяти, размера файла подкачки и значения текущей выделенной памяти на момент запуска программы. Если вы используете 32-битную версию Windows и хотите увидеть, как поведет себя система при достижении предела на выделенную память, просто запустите несколько экземпляров Testlimit до тех пор, пока один из них не достигнет предела выделенной памяти до того, как исчерпает свое адресное пространство.

Здесь следует отметить, что по умолчанию файл подкачки настроен так, чтобы увеличиваться в размерах. Это означает, что предел для выделенной памяти также будет расти по мере приближения к нему значения выделенной памяти. И даже тогда, когда файл подкачки достигнет своего максимального размера, Windows может освободить еще немного памяти, которую она зарезервировала, аналогично тому, как некоторые приложения создают кэш данных. Testlimit знает об этом, и когда она достигает предела для выделенной памяти, приложение несколько секунд бездействует, а затем пытается получить еще немного памяти. Приложение будет повторять попытки до тех пор, пока вы его не отключите.

Если вы запустите 64-битную версию Testlimit, то она почти наверняка достигнет предела для выделенной памяти до того, как исчерпает свое адресное пространство, если, конечно, размеры файлов подкачки и физической памяти не дают в сумме более 8 Тб, которые, как описано ранее, являются пределом 64-битного адресного пространства, доступного приложению. Вот фрагмент результатов работы 64-битной версии Testlimit, запущенной на моей системе с 8Гб памяти (я указал размер на выделяемую память в 100 Мб, чтобы быстрее достигнуть предела):

vm14

А вот график для выделяемой памяти, на котором шаги соответствуют паузам в работе Testlimit, сделанным для того, чтобы файл подкачки смог увеличиться:

vm15

Когда у системы остается мало виртуальной памяти, приложения могут зависать и вы можете увидеть странные сообщения об ошибке, когда выполняете стандартные операции. Тем не менее, в большинстве случаев Windows выводит диалоговое окно, сообщающее о недостатке виртуальной памяти, наподобие того, которое система показала мне во время проведения теста:

vm16

После того, как вы закроете Testlimit, ограничение на выделенную память, скорее всего, вернется на прежний уровень, когда диспетчер памяти уменьшит размер файла подкачки. На следующем снимке экрана Process Explorer показывает, что предел для выделенной памяти находится ниже того пика, который был достигнут во время работы Testlimit:

 

 

 

 

 

vm17

Память, выделяемая процессу
Поскольку предел для выделенной памяти является общим показателем для всей системы, достижение которого может привести к общему снижению производительности, ошибкам приложений или даже к зависанию системы, вполне логичным выглядит вопрос "сколько выделенной памяти должно быть выделено процессу"? Чтобы точно ответить на этот вопрос, вам нужно знать, какие еще типы виртуальной памяти могут быть заняты процессом.

Не вся виртуальная память, которой владеет процесс, имеет отношение к пределу выделенной памяти. Как вы уже знаете, примером здесь может служить зарезервированная виртуальная память. Виртуальная память, которая представляет файл на диске (такое представление носит название File Mapping View), также не участвует в расчете этого предела, за исключением случаев, когда приложение требует семантики copy-on-write, потому что Windows может стереть любые данные, связанные с образами в физической памяти, а после восстановить их из этого файла. Поэтому виртуальная память из адресного пространства Testlimit, в которую отображаются ее исполнительные и системные DLL, не учитывается при расчете предела выделенной памяти. Есть два типа виртуальной памяти процесса, которые учитываются при определении границы выделенной памяти: эти области называются "private" и "pagefile-backed".

Закрытая виртуальная память является тем видом виртуальной памяти, на основе которого строятся Garbage Collector Heap, Native Heap и Language Allocator. Она называется закрытой, потому что к ней не может быть открыт общий доступ для других процессов. По этой причине ее легко приписать к процессу, а Windows может отслеживать ее использование с помощью счетчика Private Bytes. Process Explorer отображает объем используемой закрытой памяти процесса в графе Private Bytes секции Virtual Memory на страницу Performance диалогового окна свойств процесса, а также отображает эти показатели в графическом виде на странице Performance Graph. Вот как выглядел Testlimit64 после достижения предела выделенной памяти:

vm18

vm19

Виртуальную память Pagefile-backed сложно отнести к конкретному процессу, потому что она может одновременно использоваться несколькими процессами. Фактически, нет никакого счетчика, привязанного к одному конкретному процессу, позволяющего посмотреть, сколько памяти этому процессу выделено или на какой объем этой памяти он ссылается. Когда вы запускаете утилиту Testlimit с параметром -s, она начинает занимать участки виртуальной памяти pagefile-backed до тех пор, пока не достигнет предела для выделенной памяти, но даже когда объем выделенной памяти превысит 29 Гб, статистика потребления виртуальной памяти для данного процесса не будет содержать ни одного указателя на то, что он один ответственен за это:

vm20

Именно по этой причине в программу Handle я добавил параметр -l. Процесс должен открыть объект в виртуальной памяти pagefile-backed, носящий название секции, для создания отображения виртуальной памяти pagefile-backed в его адресном пространстве. Так как Windows сохраняет существующую виртуальную память, даже если приложение закроет дескриптор к секции, из которой он был создан, большинство приложений сохранят этот дескриптор открытым. Параметр -l отображает размеры областей для секций pagefile-backed, которые открыты процессами. Вот часть результатов работы Handle для утилиты Testlimit, после того, как она была запущена с параметром -s:

vm21

Как видите, Testlimit занимает память pagefile-backed блоками по 1 Мб, и если просуммировать размер всех блоков, открытых этой программой, можно увидеть, что она является одним из процессов, занявших наибольший объем выделенной памяти.

Насколько большим следует сделать файл подкачки?
Возможно, наиболее частым вопросом, связанным с виртуальной памятью, является вопрос "насколько большим следует сделать файл подкачки?". Ни в сети, ни в информационных изданиях, освещающих вопросы Windows, нет конкретного ответа на этот вопрос, и даже Microsoft опубликовала на этот счет довольно запутанные рекомендации. Почти все советы основаны на умножении объема оперативной памяти на некоторое значение, например, на 1.2, 1.5 или 2. Теперь, когда вы понимаете, какую-роль играет файл подкачки в определении системного лимита на выделенную память и как процессы влияют на объем выделенной памяти, мы легко можете увидеть, насколько бесполезны такие формулы в действительности.

Так как предел для выделенной памяти устанавливается на основе того, сколько закрытой и pagefile-backed виртуальной памяти может быть одновременно выделено выполняющимся процессам, единственный способ правильно установить размер файла подкачки заключается в том, чтобы узнать, какой максимальный объем выделенной памяти может быть занят программами, которые вы часто запускаете одновременно. Если предел для выделенной памяти будет меньше этого значения, то ваши программы не смогут получить необходимую им виртуальную память и будут некорректно работать.
Так как же узнать, сколько выделенной памяти требуется вашей рабочей среде? На снимках экрана вы, возможно, заметили, что это число отслеживается Windows и Process Explorer показывает его: Peak Commit Charge. Чтобы установить оптимальный размер для вашего файла подкачки, вы должны запустить все приложения, с которыми вы работаете одновременно, загрузить типичный для вас объем данных и посмотреть пиковое значения выделенной памяти (или же посмотреть это значение по прошествии некоторого времени, когда, по вашему мнению, будет достигнута максимальная загруженность памяти). В качестве минимального размера файла подкачки установите это значение, отняв от него размер установленной на вашей системе оперативной памяти (если получиться отрицательное значения, выберите размер, позволяющий сохранить возможное количество отказов на минимальном уровне). Если вы хотите сохранить некоторый запас для потенциально больших расходов выделенной памяти, в качестве максимума установить это значение, умноженное на 2.

У вас может сложиться впечатление, что отсутствие файла подкачки может благотворно сказаться на производительности, однако в общем случае то, что у Windows в распоряжении будет файл подкачки, означает, что ОС сможет размещать некоторые записи (которые используются нечасто и не сохранены на диск) в файл подкачки, освобождая тем самым память для более полезных задач (процессы и кэши файлов). Так что даже если в некоторых случаях отсутствие файла подкачки может увеличить производительность, в общем случае его наличие означает, что в распоряжении системы будет больше доступной памяти (Windows в случае сбоя не сможет сделать дамп памяти, занятой под процессы ядра, если в ее распоряжении не будет достаточно большого файла подкачки).
Настройки файла подкачки находятся в окне Свойства системы, которое вы можете открыть, введя в окне Выполнить строчку "sysdm.cpl", после этого, зайдя на вкладку Дополнительно, нажать кнопку Параметры раздела Быстродействие, затем на вкладке Дополнительно нажать кнопку “Изменить”:

 

 

 

 

vm22

Там вы можете заметить, что по умолчанию Windows сама управляет размером файла подкачки. Когда это опция установлена в Windows XP и Server 2003, ОС создает один файл подкачки, минимальный размер которого равен 1,5 объема ОП системы; если объем оперативной памяти больше 1 Гб, то такой же размер устанавливается для файла подкачки; максимальный размер файла подкачки равен трем объема ОП. В Windows Vista и Server 2008 минимальное значения должно быть достаточно для того, чтобы в случае сбоя системы сделать дамп памяти, занятой под процессы ядра, и равно ОП+300 Мб или 1 Гб, в зависимости от того, какое значение окажется больше. Максимально значение - три объема оперативной памяти или 4 Гб, в зависимости от того, что окажется больше. Это объясняет тот факт, что на моей 64-битной системе с 8 Гб ОП пиковое значения выделенной памяти равно 32 Гб.

Другими ограничениями, связанными с виртуальной памятью Windows, являются максимальный размер и количество файлов подкачки. Для 32-битной Windows максимальный размер файла подкачки равен 16 Тб (4 Гб, если вы по какой-то причине работаете в режиме non-PAE), а 64-битная Windows может иметь файл подкачки размером до 16 Тб для x64 и 32 Тб в случае IA64. Для всех версий Windows есть возможность создавать до 16 файлов подкачки, каждый из которых должен располагаться на разных томах.

Оригинал записи