I was recently reading a weblog post on Linux and measuring memory used by individual applications, and it reminded me just how complex most OSes' memory management really is. Just when you think you've got it, another spanner hits you on the shin. So, let's have a look at Windows' memory management:
- Non-paged pool
- Paged pool
- Driver code
- Kernel code
- System cache
- Applications
- "Available"
All physical memory (available to the OS - we're ignoring anything that the BIOS is owning here) is used by one of the 7 categories above. Applications and "available" are the most interesting ones, and there is a reason I'm quoting "available", which I'll come to later.
Items 1-5 are all kernel memory, and the remaining two user memory.
1 Non-paged pool
Drivers are allowed to allocate memory from two pools; this is the lesser-used one, and for good reason - all memory allocated from the non-paged pool is, not surprisingly, never paged out. It is useful for operations that require this, such as DMA transfers, but not a very good thing to use for normal driver allocations.
You can see the size of this pool in Task Manager (Kernel Memory, Nonpaged) or with Performance Monitor (counter \\.\Memory\Pool Nonpaged Bytes).
2 Paged pool
The normal driver memory pool; all this memory can be paged out at the kernel's whim, and is ideal for storing driver structures that aren't used by hardware.
You can see the total allocated size of this pool in Task Manager (Kernel Memory, Paged) or with Performance Monitor (counter \\.\Memory\Pool Paged Bytes). You can see the currently paged-in size with Performance Monitor (counter \\.\Memory\Pool Paged Resident Bytes).
3 Driver code
This is the pageable memory being used to hold drivers, both their code and data pages.
You can see the size of this with Performance Monitor (counters \\.\Memory\System Driver Total Bytes and \\.\Memory\System Driver Resident Bytes).
4 Kernel code
This is the pageable memory being used to hold the kernel's own code (e.g. nsoskrnl.exe, hal.dll, boot drivers and boot file systems).
You can see the size of this with Performance Monitor (counters \\.\Memory\System Code Total Bytes and \\.\Memory\System Code Resident Bytes).
5 System cache
This is the area of kernel memory used for caching, mostly the file cache. This memory is never paged out, it is just freed (paging out a cache would be rather pointless).
You can see the size of this with Performance Monitor (counters \\.\Memory\System Cache Resident Bytes).
6 Applications
This area is defined by one book as "total memory minus the other 6 components", which is hardly surprising - it is a really hard number to track, and for various reasons there is no Performance Monitor counter for this. |\.\Process(_Total)\Working Set will usually come close, but can just as easily be larger than the real applications usage as it can smaller.
The reason it is so hard to calculate this value is simple: shared memory. This is the same reason that ps is "wrong" on Linux, as explained pretty well in the article I mentioned at the start of this post. For exactly the same reasons, Task Manager and Performance Monitor will "lie" about the memory usage.
Shared memory can be an executable (EXE, DLL, etc.) loaded into more than one process space, or specially allocated "shared memory" which is sometimes used for inter-process communications.
Each process has a working set (counter \\Process(*)\Working Set) which reports the total amount of physical memory used by that process. Task Manager calls this column "Mem Usage". Some of this memory is almost certainly shared with other processes, and thus the total of all working sets will be greater than the actual amount used.
Each process also has private bytes (counter \\Process(*)\Private Bytes) which reports the amount of allocated memory specific to this process, e.g. malloc and other direct allocations. Task Manager calls this column, rather confusingly, "VM Size". None of this memory is shared, but you also cannot tell how much of the working set is private bytes, and how much is shared bytes. (Note that a DLL loaded into a single process is still 'shared' memory, and not private bytes, because it can be shared.)
Process Explorer tries to help things by listing "WS Private", "WS Shareable" and "WS Shared", which it does by collecting data from all processes via a kernel driver. In this case, WS Private + WS Shareable = Working Set. WS Shared is just how much of the memory that can be shared is actually being shared.
I should also note that drivers can allocate memory which falls into this group, and is impossible to track as far as I know. Very few can, or do; the most notable example is Virtual PC, which allocates the VMs' memory like this.
7 "Available"
You can see the size of this group in Task Manager (Physical Memory, Available) or with Performance Monitor (counter \\.\Memory\Available Bytes).
It is not, however, as simple as it appears. Not all of the "available" memory is free memory, and therein lines the subtlety.
"Available" memory is made up 3 types:
- Standby List
- Free List
- Zeroed List
In Windows Vista, there are some new Performance Monitor counters to measuring the size of these three types, \\.\Memory\Standby Cache Core Bytes, \\.\Memory\Standby Cache Normal Priority Bytes, \\.\Memory\Standby Cache Reserve Bytes and \\.\Memory\Free & Zero Page List Bytes).
When Windows want to trim a process' working set, the trimmed pages are moved (usually) to the Standby List. From here, they can be brought back to life in the working set with only a soft page fault (much faster than a hard fault, which would have to talk to the disk). If a page stays in the standby List for a long time, it gets freed and moved to the Free List.
In the background, there is a low priority thread (actually, the only thread with priority 0) which takes pages from the Free List and zeros them out. Because of this, there is usually very little in the Free List.
All new allocations always come from the Zeroed List, which is memory pages that have been overwritten with zeros. This is a standard part of the OS' cross-process security, to prevent any process ever seeing data from another. If the Zeroed List is empty, Free List memory is zeroed and used or, if that is empty too, Standby List memory is freed, zeroed, and used. It is because all three can be used with so little effort that they are all counted as "available".
Observed Behaviours
With all that above information, you might now be able to guess what it going on when you minimize an application and see "Mem Usage" in Task Manager drop by a large amount, and why restoring the application soon after doesn't usually involve any disk paging.