What Does Windows' "Memory Usage" Actually Mean? — Reading Working Set, Private Bytes, Commit, and the Page File Correctly
· Updated: · Go Komura · Windows, Windows Development, Memory Management, Working Set, Private Bytes, Commit, Page File, Performance Monitoring, Bug Investigation, Sysinternals
Revision history (first version, published Aug 4, 2026)
- First published
Cite this article(DOI (registered archive): 10.5281/zenodo.22170853)
The DOIs below refer to previously archived versions and may not match the current text. Use this page’s URL to reference the current text.
Go Komura (2026). What Does Windows' "Memory Usage" Actually Mean? — Reading Working Set, Private Bytes, Commit, and the Page File Correctly. KomuraSoft LLC. https://comcomponent.com/en/blog/windows-memory-usage-working-set-commit/
- DOI (registered archive)
- 10.5281/zenodo.22170853
- DOI (last registered version)
- 10.5281/zenodo.22170854
Task Manager shows a process’s “Memory” as 1.2GB. Yet Process Explorer shows a Working Set of 1.5GB and Private Bytes of 2.4GB, and in VMMap the Size is larger still. Looking at the system as a whole, it reads “Committed 19.6/31.8GB”.
So how many gigabytes of memory is this app actually using?
The fact that these numbers differ does not tell you that any of them is wrong. Which metric to look at depends on what you want to know.
The amount currently in RAM, the amount allocated privately to that process, the amount the system has promised to keep backing in the future, and the range of virtual addresses that has been reserved are all different things. Before asking “how many GB?”, first establish “the amount of what?”.
Windows memory metrics are hard because they are all displayed under the same word, “memory”, even though they actually measure the following separate axes.
- How much address space is in use
- How much commit has been consumed
- Whether the page is currently resident in physical RAM
- Whether the page is private to the process or shareable
- How much more allocation the system as a whole can still support
This article is for people investigating growing application memory or system-wide memory shortages on Windows 10/11 and current Windows Server. It connects Working Set, Private Working Set, Private Bytes, Commit, Virtual Bytes, the page file, Available, and page faults into a single picture.
The procedure for tracking down why .NET objects are not being collected is covered in “Telling GC Latency From a Memory Leak in .NET”, and the concrete operation of VMMap and Process Explorer in “Process Explorer / Handle / VMMap in Practice”. This article concentrates on the prerequisite for both: how to read the numbers on the Windows OS side.
1. The Bottom Line First
Working Set is “the amount in RAM right now”, Private Bytes is “the amount promised privately to this process”, and System Commit is “the amount the whole system has promised”. Start by reading these three separately.
Match Each Metric to the Question It Answers
| What you want to know | Metric to look at | Caution when reading it |
|---|---|---|
| How much of the target process is in RAM right now | Working Set | Includes not only the process’s private pages but also shared pages such as DLL code and memory-mapped files1 |
| Of that, how much RAM is used only by that process | Private Working Set | An approximation of “the RAM this process privately occupies right now”, not the total the app has allocated2 |
| How much commit is private to the target process | Private Bytes | Does not depend on whether the pages are resident in RAM. It is also not the number of bytes actually written to the page file2 |
| Whether the system-wide commit has headroom | “Committed X/Y” | X is the current amount, Y is the limit. X is not page file usage, and Y is determined roughly by RAM plus the page file3 |
Watch out for the Win32 API name PagefileUsage as well. On current Windows it effectively represents the same Commit Charge as Private Bytes, not the amount actually written to the page file.2
Do Not Judge From “Allocated” or “Increased” Alone
If a virtual address range has only been reserved, it has merely been set aside for future use. It does not consume the same amount of RAM or Commit Limit; Reserve and Commit are separate stages.45
Also, a page fault is not necessarily disk I/O. Distinguish soft faults, which can be resolved within RAM, from hard faults, which read from the page file, an executable, a memory-mapped file, or similar.16
A memory leak, too, cannot be judged from one large value. Look at whether, when the same load is repeated, Private Bytes or its breakdown keeps stepping up after each run instead of returning to the same steady state. What you look at is not the size at one moment but the floor and the slope after repetition.
Read by Symptom or Goal
| What is bothering you right now | Where to read |
|---|---|
| The numbers differ from tool to tool and you do not know which to trust | Chapter 2: The four axes that separate the metrics, Chapter 9: Choosing screens and tools |
| OutOfMemory occurs even though free RAM is available | 3.4: Allocation constraints other than RAM, Chapter 6: The system-wide Commit Limit |
| Working Set or Private Bytes keeps growing | Chapter 4: Growth and shrinkage of RAM residency, Chapter 5: Private Bytes and release |
| You are wondering whether to shrink or disable the page file | 6.3: The role and size of the page file |
| RAM usage is high but no large process is visible | Chapter 7: The breakdown of physical RAM |
| Page Faults/sec is high and you want to know whether memory is short | Chapter 8: The difference between soft and hard faults |
| You want to narrow down the cause from recorded numbers and investigate a leak | Chapter 10: Combinations of numbers, Chapter 11: Leak investigation procedure |
To learn the mechanism from the beginning, read in order from Chapter 2; if you already have recordings, start from the table in Chapter 10.
flowchart TB
accTitle: Choosing among the main Windows memory metrics
accDescr: The metric to look at changes depending on whether you want to know RAM residency, process-private commit, system-wide commit, or the virtual address range
question["What do you want to know from 'memory usage'?"]
question -->|Amount in RAM right now| workingSet["Working Set"]
question -->|Amount promised privately to the process| privateBytes["Private Bytes"]
question -->|Amount promised system-wide| systemCommit["System Commit"]
question -->|Address range reserved| virtualBytes["Virtual Bytes / Reserved"]
workingSet --> resident["Residency in physical RAM"]
privateBytes --> privateCommit["Process-private Commit"]
systemCommit --> commitLimit["Compare with Commit Limit"]
virtualBytes --> addressSpace["Virtual address space"]
Figure 1: Break the observation “memory is high” down into four kinds of questions first.
In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (18 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle
2. Splitting “Memory Usage” Into Four Axes
You can make sense of the differing numbers by thinking of them as counting the same pages from different angles. Here we split them into four axes: address state, backing, residency in RAM, and shareability with other processes.
flowchart TB
accTitle: Four independent axes for classifying a page
accDescr: Check the virtual address state, the backing of a committed page, residency in physical RAM, and shareability with other processes separately
page["One page seen along four axes"]
page --> address["Address state"]
address --> addressValues["Free / Reserved / Committed"]
page --> backing["Backing"]
backing --> backingValues["Page-file-backed / File-backed"]
page --> residentAxis["RAM residency"]
residentAxis --> residentValues["Resident / Not resident"]
page --> sharing["Shareability"]
sharing --> sharingValues["Private / Shareable"]
Figure 2: Even for a single page, address state, backing, residency, and shareability are decided separately.
Do Not Mix Up State, Backing, and Shareability
Mapped is not an address state alongside Free, Reserved, and Committed; it is a kind of region. Pages of a mapped view can be Committed too.
Private, on the other hand, is not a backing medium but a classification of shareability. Read backing as page-file-backed or file-backed, and shareability as Private or Shareable, separately.
Compare Which Metrics Count a Page
Combining these four axes, the relationships among the representative metrics look like this.
| Page state | Working Set | Private Working Set | Private Bytes | Virtual Bytes family |
|---|---|---|---|---|
| Process-private, committed, resident in RAM | Included | Included | Included | Included |
| Process-private, committed, not resident in RAM | Not included | Not included | Included | Included |
| Shared page of a DLL or mapped file, resident in RAM | Included | Generally not included | Generally not included | Included |
| Reserved but not committed | Not included | Not included | Not included | May be included |
| Unused address range | Not included | Not included | Not included | Normally not included |
flowchart TB
accTitle: Mapping page kinds to the main memory metrics
accDescr: Shows which metrics include private resident pages, private non-resident pages, shared resident pages, and reserved-only ranges
privateResident["Private, committed, resident in RAM"]
privateNonresident["Private, committed, not resident in RAM"]
sharedResident["Shared page, resident in RAM"]
reservedOnly["Reserved, not committed"]
workingSet["Working Set"]
privateWorkingSet["Private Working Set"]
privateBytes["Private Bytes"]
virtualBytes["Virtual Bytes family"]
privateResident --> workingSet
privateResident --> privateWorkingSet
privateResident --> privateBytes
privateResident --> virtualBytes
privateNonresident --> privateBytes
privateNonresident --> virtualBytes
sharedResident --> workingSet
sharedResident --> virtualBytes
reservedOnly --> virtualBytes
Figure 3: Working Set and Private Bytes count different sets of pages, so there is no simple containment between them.
There Is No Fixed Ordering Between Working Set and Private Bytes
The important point here is that Working Set and Private Bytes are not in a simple containment relationship.
Private Bytes includes pages that are private to the process but not currently resident in RAM. Working Set, meanwhile, includes shared pages such as DLL code and shared memory that Private Bytes does not count. Depending on the process and the moment, Working Set can therefore be larger than Private Bytes, and the reverse can also be true.
Also, simply adding up the Working Sets of several processes may count the same physical page, such as a shared DLL, more than once. “The sum of every process’s Working Set equals RAM in use” does not necessarily hold.
3. Virtual Address Space — Reserve and Commit Are Different Things
“Reserving an address”, “committing”, and “touching a page for the first time so it lands in RAM” are separate events. This chapter explains those differences and, from there, why an allocation can fail even when free RAM is available.
3.1. A Virtual Address Is Not a Physical RAM Address
Each process has its own virtual address space. The pointers an app handles do not directly indicate a location in physical RAM; Windows uses page tables to map virtual addresses to physical pages or to data in a file.7
This is why, even on a PC with 64GB of RAM, the virtual address space available to a given 32-bit process is normally far smaller than that. Conversely, a 64-bit process with a virtual address space larger than physical RAM is perfectly ordinary.
3.2. Reserved Only Means “the Address Has Been Claimed”
VirtualAlloc with MEM_RESERVE claims a contiguous range of virtual addresses for future use. At this stage no physical storage is associated with the pages, and the range cannot be read or written.45
For example, even if a database or runtime has reserved an 8GB address range for future growth, that by itself does not mean it has consumed 8GB of RAM or 8GB of Private Bytes.
3.3. Committed Is a Promise to “Back It When It Is Needed”
MEM_COMMIT puts the virtual pages into the Committed state, and Windows promises to provide the necessary backing. At the moment of the commit, the amount is charged against the system’s Commit Charge.5
Committed Does Not Necessarily Mean Readable or Writable
Read, write, and execute permission is decided separately by page protection such as PAGE_READONLY, PAGE_READWRITE, PAGE_EXECUTE, and PAGE_NOACCESS. Being committed does not by itself mean “readable and writable”.5
Commit and Residency in RAM Are Also Separate Stages
The actual physical page may not be assigned until first access. A page touched for the first time is zero-initialized and enters the Working Set through a demand-zero fault.51
So the same word “allocated” covers the following three stages.
flowchart TB
accTitle: The three stages from Reserve through Commit to RAM residency
accDescr: Shows the flow in which a virtual address is reserved, the page is committed, and a physical page is assigned on first access and enters the Working Set
reserve["MEM_RESERVE claims an address range"]
reserve -.-> virtualMetric["Reflected in the Virtual Bytes family"]
reserve -->|MEM_COMMIT| committed["Committed, accessible according to page protection"]
committed -.-> commitMetric["Reflected in Private Bytes / System Commit"]
committed -->|First access, demand-zero fault| resident["Physical page assigned, resident in RAM"]
resident -.-> workingSetMetric["Reflected in Working Set"]
committed -.->|If never accessed| nonresident["Committed but not resident"]
Figure 4: Reserve, Commit, and first access are separate events, and each moves a different metric.
These three stages move the Virtual Bytes family, Private Bytes, and Working Set numbers independently.
3.4. Why OutOfMemory Can Occur Even With Free RAM
Whether a memory allocation succeeds is not decided by free RAM alone.
- The process has used up its virtual address space
- There is no contiguous free address range of the required size
- The system-wide Commit Charge has reached the Commit Limit
- A Job Object, container, runtime, or library imposes its own limit
- The process is 32-bit
- The native heap is fragmented
Even on 64-bit Windows, the user-mode virtual address space of a 32-bit process is normally 2GB when IMAGE_FILE_LARGE_ADDRESS_AWARE is not set. A 32-bit app with the flag set can use up to 4GB on 64-bit Windows.8
So “the PC has 20GB of free RAM, yet the 32-bit app fails at around 1.6GB” is not a contradiction. It is not a RAM problem; the app is probably hitting address space fragmentation or the address space limit.
4. Working Set — Pages Currently in RAM
The Working Set is the set of pages in a process’s virtual address space that are currently resident in physical RAM.1
It mixes together the following.
- The process’s private heaps and stacks
- Code and read-only data of the EXE and DLLs
- Memory-mapped files
- Shared memory
- Pages that became private to the process after copy-on-write
- Pages touched by the runtime and various libraries
4.1. A Growing Working Set Does Not Necessarily Mean More Was Allocated
When It Grows, Existing Pages May Simply Have Entered RAM
If already committed pages are accessed for the first time, Working Set alone may grow while Private Bytes stays unchanged.
Likewise, if a large file is memory-mapped and read sequentially, file-backed pages enter the Working Set while Private Bytes barely grows.
When It Shrinks, the Same Memory May Still Be Held
When Windows trims the Working Set in response to memory pressure, only the Working Set shrinks while the app logically still holds the same memory. When the pages are touched again later, they come back through page faults.
In other words, a rise in Working Set does not necessarily mean “newly allocated”, and a drop does not necessarily mean “released”. Read changes in residency separately from allocation and release.
flowchart TB
accTitle: The typical flow in which only Working Set rises and falls
accDescr: The same committed page enters RAM on first access, becomes non-resident when trimmed, and returns on re-access, while Private Bytes continues to be charged throughout
committed["The same committed page"]
committed -->|First access| resident["Resident in RAM"]
resident -->|Trimmed under memory pressure| nonresident["Not resident"]
nonresident -->|Page fault on re-access| resident
resident -.-> inWorkingSet["Included in Working Set"]
nonresident -.-> outsideWorkingSet["Not included in Working Set"]
committed -.-> privateBytes["Charged to Private Bytes while committed"]
Figure 5: Working Set rises and falls with residency, but as long as the same page’s Commit remains, Private Bytes does not drop.
4.2. Working Set Includes Shared Pages
If ten processes share the code pages of the same DLL, those pages can appear in each process’s Working Set, yet only one copy may exist in physical RAM. The sum of the Working Sets exceeding installed RAM is not immediately abnormal.
To get closer to “the RAM this process alone privately occupies right now”, look at Private Working Set. But this too is not “all the memory the process has allocated”; it is only the private pages currently resident.
4.3. Forcing the Working Set Down Does Not Fix a Leak
With EmptyWorkingSet or SetProcessWorkingSetSize, you can push pages out of a process’s Working Set. But this is not an operation that releases commit or references on the heap. Only the apparent RAM usage drops while Private Bytes stays unchanged, and page faults may increase on the next access.9
If the Task Manager number drops only right after a “reduce memory” button is pressed and comes straight back once you resume working, it may just have trimmed the Working Set rather than “released” anything.
5. Private Bytes — The Commit Private to a Process
Private Bytes is the amount of virtual memory committed exclusively for that process. It represents the Commit Charge that cannot be shared with another process, regardless of whether it is currently resident in RAM. In Microsoft’s PROCESS_MEMORY_COUNTERS_EX, PrivateUsage corresponds to this value.102
The Win32 API also has the confusingly named field PagefileUsage, but the current documentation defines it as “the Commit Charge of that process” and explains that it is the same value as PrivateUsage. In other words, Private Bytes of 2GB does not mean “2GB has been written to pagefile.sys”.2
Private Bytes is typically affected by the following.
- Commit of native heaps used by
HeapAlloc,malloc,new, and the like - Private Data committed directly with
VirtualAlloc - The committed region of the .NET GC heap
- The part of each thread stack that has actually been committed
- The Commit Charge for the entire view that is claimed when a copy-on-write view (
FILE_MAP_COPY) is mapped - Private buffers held internally by libraries and device SDKs
Note also that with copy-on-write, the charge is taken before any actual write. Because every page of a view created with FILE_MAP_COPY may become private in the future, Commit Charge is claimed at mapping time so that the entire view can be backed by the page file.11
As a result, System Commit and the process’s Commit Charge (Private Bytes) can grow by the full size of the view even before anything is written and a private copy is created.11
5.1. Why Private Bytes Does Not Drop After free or a GC
Separate Release Within the App From Returning Memory to the OS
Even when memory is “released” from the application’s point of view, the runtime or heap allocator may keep the region for future reuse instead of decommitting it to the OS. In that case the memory is reusable inside the app, but Private Bytes stays high.
Private Bytes also stays high when only part of a large region survives, when the heap is fragmented, or when caches and pools have warmed up to their limits.
Compare the Change After the Same Load, Not the High Plateau
High Private Bytes alone does not prove a leak. Compare over time in the following order.
- Repeat the same operation the same number of times.
- Wait the same amount of time after each run.
- Check whether Private Bytes returns to the same level or levels off at a fixed value.
- Check with VMMap or a heap dump which region or type grew.
flowchart TB
accTitle: Why Private Bytes does not drop after free or a GC
accDescr: Private Bytes changes differently depending on whether the allocator returns a region the app no longer needs to the OS or keeps it for reuse
release["App discards memory via free / GC"]
release --> decision{"Does the allocator return it to the OS?"}
decision -->|Decommit / Release| returned["Commit Charge decreases"]
returned --> lower["Private Bytes drops"]
decision -->|Kept for reuse| retained["Region stays committed"]
retained --> high["Private Bytes stays high"]
retained --> reasons["Pools, caches, fragmentation"]
Figure 6: Becoming reusable inside the app is not the same as returning Commit to the OS.
5.2. The Shape That Strongly Suggests a Leak
A “staircase” increase like the following, where the floor rises with every load, deserves attention.
Private Bytes
^
| ________
| ______|
| ______|
|_____|
+----------------------------> repetitions of the same operation
That said, even a staircase can rise for only the first few runs because of initial JIT, fonts, image decoders, connection pools, or cache warm-up, and then stabilize. What matters is not that it is growing but that it does not converge to a steady state.
6. System Commit — What “Committed X/Y” Really Is
So far we have mainly looked at metrics for a single process. “Committed X/Y” under Performance > Memory in Task Manager is a system-wide metric. Read it separately from the per-process values.
- X: System Commit Charge
The committed memory Windows is currently promising to back across the whole system - Y: System Commit Limit
The upper limit of commit the system can support
The Commit Limit is determined roughly by physical RAM plus all page files combined. Without a page file, it is slightly smaller than installed RAM.36
flowchart TB
accTitle: The relationship between System Commit Charge and Commit Limit
accDescr: Process-private, shared-section, and kernel commit make up the current value X, while physical RAM and the page file support the limit Y
processCommit["Private Commit of each process"] --> charge["System Commit Charge (X)"]
sharedCommit["Commit of page-file-backed shared sections"] --> charge
kernelCommit["Kernel Commit"] --> charge
physicalRam["Physical RAM"] --> limit["System Commit Limit (Y)"]
pageFiles["Page files"] --> limit
charge -->|X cannot exceed Y| limit
Figure 7: X is the amount currently promised, Y is the limit that can support the promise, and neither is a display of page file usage.
System Commit Charge includes not only the sum of every process’s Private Bytes but also the Commit of shared sections backed by the page file and the Commit consumed by the kernel. The sum of per-process Private Bytes therefore cannot fully explain X on its own.
6.1. Commit Charge Is Not Page File Usage
The 20GB in “20/31GB” Is Not an Amount on Disk
Consider a system with 16GB of RAM, a 16GB page file, and Committed at 20/31GB.
This 20GB does not mean “20GB has been written to the page file”. The 20GB is the total that Windows has promised to back, with RAM, the page file, or similar, when needed, for private modifiable pages and the like.
Within that 20GB, the following states are mixed together.
- Most of it is resident in RAM
- Some of it has been moved out to the page file
- Some is committed but has not yet been accessed for the first time
- Some is consumed as commit on the kernel side
Look at Page File Usage With a Different Counter
To see the actual utilization of the page file, check Paging File(*)\% Usage separately from Commit. Even Microsoft’s documentation explains, however, that high page file usage alone does not necessarily indicate a performance problem, and that it should be judged together with whether the Commit Limit is being reached, the Modified Page List, and actual paging I/O.6
6.2. What Happens as the Commit Limit Approaches
When the System Commit Charge reaches the Commit Limit, new commit requests cannot be supported. This leads to failed memory allocations in processes, app crashes, and an unresponsive system.3
Here the X/Y of Commit matters more than “free RAM”. Even if you trim Working Sets to create free RAM, reaching the Commit Limit is not resolved unless the Commit Charge itself decreases.
6.3. The Three Roles of the Page File
The page file mainly has the following roles.
- Extending the Commit Limit
- Allowing infrequently used modified pages to be moved out of RAM
- Supporting system crash dumps, depending on the configuration
Decide on Disabling or Resizing From Peak Commit and Dump Requirements
Disabling the page file is not a simple matter of “disk I/O always decreases, so it gets faster”. On the contrary, it lowers the Commit Limit, makes it more likely that modified pages that will not be used for a while stay in RAM, and may make it impossible to capture the dump you need at crash time.36
The appropriate page file size is not determined by installed RAM alone. Microsoft also explains that it cannot be generalized, because peak System Commit Charge and the kind of crash dump required differ from system to system.6
7. The Breakdown of Physical RAM — Do Not Judge From Low Available Alone
Physical RAM is not used only for the Working Sets of user processes.
- The Working Set of each process
- The system file cache
- Page lists such as Standby, Modified, Free, and Zeroed
- The kernel’s Paged Pool and Nonpaged Pool
- Memory held by device drivers
- The memory compression store
- Regions shared with or reserved for the GPU and devices
- Hardware reservations
7.1. Available Also Includes Reusable Cache
Free and Available do not mean the same thing. Windows’ Available MBytes includes not only Free and Zeroed but also Standby pages that can be reused when needed. It is not a metric that counts only completely unused RAM.12
- Free: pages not currently assigned to any purpose
- Zeroed: pages already zeroed so they can safely be handed to another process
- Standby: pages removed from a Working Set whose contents are still cached in RAM
- Modified: pages whose contents have changed and must be written back to the appropriate backing before reuse
flowchart TB
accTitle: Movement between the Working Set and the page lists
accDescr: Shows the flow in which an in-use page moves to Standby if unmodified or to Modified if modified, and then goes through re-access, write-back, and reuse
workingSet["Working Set (in use)"]
workingSet -->|Remove an unmodified page| standby["Standby (reuse candidate that keeps its contents)"]
workingSet -->|Remove a modified page| modified["Modified (awaiting write-back)"]
modified -->|Write-back complete| standby
standby -->|Re-access| workingSet
standby -->|Reused for another purpose| reused["Assigned to another purpose"]
free["Free (unused)"] -->|Zeroing| zeroed["Zeroed (ready for new allocation)"]
zeroed -->|Accessed after allocation| workingSet
standby -.-> available["Included in Available"]
free -.-> available
zeroed -.-> available
Figure 8: Available includes not only completely free pages but also Standby pages that can be reused when needed.
Separate Low Free From Physical Memory Pressure
“Throwing away all the cache to increase free RAM” is not always a win. If the data you need is still on Standby, it can be returned to the Working Set quickly on re-access without reading the disk.
So even if Task Manager shows little Free, as long as Available is sufficient and hard page faults and disk waits are not a problem, Windows may simply be putting RAM to good use as cache.
7.2. When RAM Shrinks Without Any Large Process
Memory consumption that cannot be explained by adding up every process’s Private Working Set is not unusual.
- The file cache and memory-mapped files
- Nonpaged Pool and Paged Pool
- Pages locked by drivers
- Shared pages
- Memory compression
- Virtualization and GPU-related allocations
In this case, rather than continuing to stare at the process list, check Use Counts, Processes, Priority Summary, and File Summary in Sysinternals RAMMap. RAMMap is the official tool for breaking physical memory down by purpose, page list, and file.13
If only Nonpaged Pool keeps growing, you are at the stage of suspecting a leak in a driver or on the kernel side, not in a user-mode app’s Private Bytes.
8. Page Faults — A High Count Is Not Abnormal by Itself
A page fault occurs when a process accesses a page that is not currently in its Working Set. Despite the word “fault” in the name, it is not an exceptional failure but the normal mechanism that drives virtual memory.1
The first thing to separate is whether the disk has to be read. On top of that, look not only at the number of occurrences but also at the actual wait time and the degradation of responsiveness.
8.1. Soft Page Faults
These can be resolved without reading the disk.
- The page is still on Standby or in Transition
- The same shared page is in another process’s Working Set
- A committed page is accessed for the first time and a zero page is assigned
- The memory manager’s read-ahead has already brought it into RAM
For this reason, a high \Memory\Page Faults/sec does not necessarily mean that disk I/O or latency is occurring.
8.2. Hard Page Faults
These require the contents to be read from a backing store on disk. The source is not limited to the page file.
- Code and data of
.exeand.dllfiles - Memory-mapped files
- The page file
flowchart TB
accTitle: The fork between a soft page fault and a hard page fault
accDescr: When a page not in the Working Set is accessed, it is handled as a soft page fault if no storage I/O is needed and as a hard page fault if it is
access["Access to a page not in the Working Set"] --> storageIo{"Is storage I/O needed?"}
storageIo -->|No, resolved from Standby, a shared page, demand-zero, etc.| soft["Soft page fault"]
soft --> resident["Into the Working Set without reading the disk"]
storageIo -->|Yes| hard["Hard page fault"]
hard --> source{"Where is it read from?"}
source --> image["EXE / DLL"]
source --> mapped["Memory-mapped file"]
source --> pagefile["Page file"]
image --> loaded["Into the Working Set after loading"]
mapped --> loaded
pagefile --> loaded
Figure 9: The name “page fault” alone does not tell you whether disk I/O was involved.
Microsoft lists \Memory\Pages/sec, \Memory\Page Reads/sec, \Memory\Pages Input/sec, and similar as counters for measuring hard faults. Because high values do not necessarily mean low memory, correlate them with Available MBytes, disk latency, and actual response time.6
8.3. Do Not Set a Blanket Threshold
A fixed value such as “Page Faults/sec above 1000 is abnormal” means different things depending on the storage, page size, workload, and access locality.
In practice, line up the following on the same timeline.
Memory\Available MBytesMemory\Pages Input/secMemory\Page Reads/sec- Read latency and queue length of the target disk
- The target process’s Working Set and Private Bytes
- The app’s processing time, timeouts, and UI responsiveness
If Available drops at the same time as the load increases, Pages Input/sec and disk waits rise, and processing time deteriorates too, you have grounds to suspect paging caused by physical memory pressure.
9. Which Screen or Tool to Use for What
Deciding first whether you are looking at one process or the whole system, and at a breakdown at one moment or a time series makes the tools easier to choose. Match metrics to tools in the table below before moving on to recording.
| What you want to know | Metric to look at first | Main tools |
|---|---|---|
| How much the target process has in RAM right now | Working Set | Task Manager, Process Explorer, Get-Process |
| Of that, the RAM private to the process | Private Working Set / Working Set - Private | Task Manager’s Details columns, Process Explorer, PerfMon |
| The commit private to the target process | Private Bytes / Commit Size | Process Explorer, PerfMon, VMMap, Get-Process |
| The process’s virtual address range | Virtual Bytes / Size | Process Explorer, VMMap, Get-Process |
| System-wide commit headroom | Committed Bytes / Commit Limit | Task Manager’s Performance tab, PerfMon |
| Reusable headroom in physical RAM | Available MBytes | Task Manager, PerfMon |
| Breakdown of Standby, Modified, and the file cache | Page lists and breakdown by purpose | RAMMap |
| What grew within Private Bytes | Heap / Private Data / Managed Heap, etc. | VMMap, WinDbg, runtime-specific dumps |
| Paging that involves the disk | Pages Input/sec, Page Reads/sec, disk latency | PerfMon, WPR/WPA |
flowchart TB
accTitle: Choosing a Windows memory investigation tool
accDescr: Choose the tool by whether the target is one process or the whole system, one moment or a time series, and whether you follow retention inside the runtime
question["What do you want to isolate?"]
question --> processScope{"Is the target one process?"}
processScope -->|Yes| processTime{"One moment or a time series?"}
processTime -->|Breakdown at one moment| vmmap["VMMap"]
processTime -->|Time series| perfmon["PerfMon / PowerShell"]
processScope -->|Whole system| systemView{"Breakdown of physical RAM or a timeline?"}
systemView -->|Breakdown of physical RAM| rammap["RAMMap"]
systemView -->|Timeline including CPU, I/O, and waits| wpa["WPR / WPA"]
question --> runtime{"Follow retention inside the runtime?"}
runtime -->|.NET heap| dotnet["dotnet-dump / PerfView"]
runtime -->|Native heap| native["WinDbg / Application Verifier"]
Figure 10: Deciding the scope and the time axis first lets you choose exactly the tools you need.
9.1. Task Manager
In Task Manager, look at the screens separately.
- Processes or Details: the Working Set family and Commit Size family for individual processes
- Performance > Memory: system-wide In use, Available, Committed, Cached, Paged pool, and Non-paged pool
Do not judge from the column name “Memory” alone; right-click the column headers on the Details tab and add the columns you need, such as Working Set, Peak Working Set, and Commit Size. Column names vary somewhat with the Windows version and display language, so confirm what a column means before recording it.
9.2. Capturing a Time Series With PowerShell
First, Record Three Metrics for the Same Process
If you know the ID of the target process, Get-Process can capture the slopes of Working Set, Private Bytes, and Virtual Bytes at the same time.
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
In .NET, Process.WorkingSet64 corresponds to Working Set, PrivateMemorySize64 to Private Bytes, and VirtualMemorySize64 to Virtual Bytes.141516
Avoid Mix-Ups From Multiple Instances or Restarts
For an app with multiple instances, track by PID, not by name. For long-term monitoring where a restart changes the PID, the design must record the start time, service name, and similar so that the wrong target is not picked up.
9.3. Putting the System and the Process on the Same Timeline With PerfMon
At a minimum, recording the following simultaneously makes isolation easier.
\Process(<target>)\ID Process
\Process(<target>)\Working Set
\Process(<target>)\Working Set - Private
\Process(<target>)\Private Bytes
\Process(<target>)\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
Check the PID of Each Sample, Not Just the Instance Name
When several processes share a name, or when the process restarts during monitoring, the instance name alone, Process(name) or Process(name#N), cannot pin down the target. Record ID Process with every sample as well, and use only the instance whose value matches the PID you are tracking. If the monitoring spans a restart that changes the PID, also record separately the time at which it switched.
If a Counter Cannot Be Found, Check the Display Language
Windows performance counter names may be localized according to the display language. If specifying the English name directly in PowerShell does not find the counter, add it from the PerfMon GUI or check the names in the local environment with Get-Counter -ListSet *.
9.4. Do Not Confuse the Roles of VMMap and RAMMap
- VMMap: breaks one process’s virtual memory and Working Set down into Heap, Image, Mapped File, Private Data, Managed Heap, and so on
- RAMMap: breaks the whole system’s physical RAM down by purpose, page list, process, and file
“What made this process’s Private Bytes grow?” is a question for VMMap; “what is the RAM that the process list cannot explain being used for?” is a question for RAMMap.1713
10. Reading Symptoms From Combinations of Numbers
Read the recorded values by which metrics move together and which stay unchanged. The following table is not for pinning down the cause; it is for choosing the breakdown or condition to check next.
| Observed shape | First thought | What to check next |
|---|---|---|
| Working Set grows, Private Bytes stable | First access to existing pages, shared DLLs, mapped files, file cache | Image / Mapped File in VMMap, Pages Input/sec |
| Private Bytes grows, Working Set stable | Private Commit grew but is non-resident or trimmed | Heap / Private Data / Managed Heap in VMMap |
| Both grow right after startup, then flatten | Warm-up of JIT, caches, pools, initialization | Whether it grows again when the same load is added |
| The floor of Private Bytes rises with every load | Leak, unbounded cache, allocator that retains after free | VMMap snapshots before and after, heap dump |
| Only Working Set drops suddenly and comes back with use | The OS or the app trimmed the Working Set | Private Bytes, Pages Input/sec, response time |
| X in Committed X/Y approaches Y | System-wide commit pressure | Top Private Bytes consumers, Paged/Nonpaged Pool, page file settings |
| Available is low, Pages Input/sec and disk latency are high | Physical RAM pressure and hard paging | Top Working Set consumers, RAMMap, correlation with the workload |
| RAM usage is high but there is no large process | Cache, shared pages, kernel pools, drivers, compression, etc. | RAMMap, Pool Nonpaged/Paged Bytes |
| Only the 32-bit app fails despite free RAM | Virtual address space limit or fragmentation | Free/Reserved in VMMap, the executable’s LAA setting |
| Private Bytes is high but does not grow with repeated operations | Possibly a pool or cache holding its high-water mark | Limits, reuse behavior, stability after the peak |
The most important thing about this table is to read combinations, not single values.
11. A Practical Procedure for Investigating a Memory Leak
The investigation proceeds in five stages: decide the comparison conditions → record simultaneously → identify the metric that grew → examine the breakdown → compare before and after the fix. Instead of jumping to a dump as soon as you find a large value, narrow down what is growing first.
11.1. First Decide the Reproduction Conditions and the Steady Point
“It grows over several days” is not something you can compare. So that the good version and the problem version can be compared under the same conditions, decide the following first.
- How much of the post-startup warm-up to include
- What one cycle of operations consists of
- How many seconds to wait after one cycle
- How many cycles it takes to reach the cache limit
- Whether the same input can be used for the good and problem versions
11.2. Record the Process and the System Simultaneously
At a minimum, keep the following at the same timestamps.
- The target’s Working Set
- The target’s Private Bytes
- The target’s Virtual Bytes
- The system’s Committed Bytes / Commit Limit
- Available MBytes
- Pages Input/sec
- Handle count, thread count
- Number of operations or items processed
If the process’s Private Bytes is stable but only the system’s Commit grows, you need to widen the field of view to other processes, the kernel, drivers, shared sections, and so on.
11.3. Decide Which “Dimension” Is Growing First
- Working Set only: resident pages, shared or file-backed pages, trimming and reloading
- Private Bytes: process-private commit
- Virtual Bytes only: reservations, mappings, address space fragmentation
- System Commit only: includes other processes and the kernel side
- Nonpaged Pool: drivers and the kernel side
- Handles / GDI / USER: a resource leak other than memory
If you skip this ordering and take a dump right away, you end up reading a large amount of information with the wrong target.
11.4. Move On to the Breakdown
- Native processes: VMMap, WinDbg, Application Verifier, heap tracing
- .NET:
dotnet-counters,dotnet-gcdump,dotnet-dump, PerfView - Whole system: RAMMap, PerfMon, WPR/WPA
- Kernel pools: PoolMon, WinDbg
VMMap displays a process’s committed virtual memory, and the Working Set assigned to each part, by type. How far you can narrow the growth in Private Bytes down to “Heap”, “Private Data”, “Managed Heap”, or “Mapped File” makes a large difference to the cost of the investigation that follows.17
11.5. After the Fix, Compare the Slope Under the Same Conditions
Different peak values before and after the fix are not enough. If the initial values differ, the comparison easily flips. Align the following conditions and compare the floor and slope after each cycle.
- The same startup state
- The same input
- The same number of operations
- The same wait time
- The same sampling interval
Proof that a leak is fixed is not “the maximum got smaller” but “the growth now converges even when the same load is repeated”.
12. Rephrasing Common Misconceptions
Misconception 1: Task Manager’s Memory Is the Total an App Has Allocated
Rephrased: Check which column it is. The Working Set family is the amount currently resident in RAM; the Commit Size family is the commit private to the process.
Misconception 2: Private Bytes Is the Number of Bytes in the Page File
Rephrased: Private Bytes is the private Commit Charge. It is a logical promised amount that includes both pages in RAM and pages that will be backed by the page file when needed.
Misconception 3: Commit X/Y Is Page File Usage / Page File Capacity
Rephrased: X is the system-wide Commit Charge, Y is the Commit Limit. The page file extends Y, but X does not translate directly into usage on disk.
Misconception 4: High Page Faults/sec Means Swapping to Disk
Rephrased: It includes soft faults too. Whether disk I/O is involved is confirmed with Pages Input/sec, Page Reads/sec, and disk latency.
Misconception 5: Low Free RAM Means Memory Is Short
Rephrased: Look at Available, Standby, hard paging, and response time. Filling RAM with reusable cache is normal.
Misconception 6: Shrinking the Working Set Means the Memory Leak Is Fixed
Rephrased: Pages may merely have been pushed out of RAM. Check whether Private Bytes and retention inside the heap have decreased.
Misconception 7: Private Bytes Grew, So It Is Definitely a Leak
Rephrased: You can judge only after checking whether it converges when the same workload is repeated, which kind of memory grew, and whether it is a releasable cache.
13. Summary
First, Decide What the Number Is Counting
Windows “memory usage” is not a single number. Think about address space, commit, RAM residency, and shareability separately.
Working Set is the pages currently in RAM and includes both Private and Shared pages. Private Working Set is the process-private resident pages among them. Private Bytes, by contrast, is the process-private Commit Charge; it is neither the amount currently in RAM nor the amount actually written to the page file.
Next, Read Allocation, Residency, and Paging Separately
Reserved virtual addresses, committed pages, and pages actually touched and brought into the Working Set are separate stages.
Committed X/Y shows the system-wide Commit Charge / Commit Limit. The page file mainly extends the Commit Limit, holds modified pages moved out of RAM, and supports crash dumps.
A page fault is normal operation, and a soft fault does not read the disk. The sources of a hard fault include not only the page file but also EXEs, DLLs, and mapped files.
In an Investigation, Compare the Floor, Slope, and Breakdown After the Same Load
A memory leak is proven not by the size at one moment but by the floor and slope after the same load, and by the breakdown.
The basic path is VMMap for the breakdown of an individual process, RAMMap for the whole system’s physical RAM, PerfMon for time series, and dedicated dump tools for the inside of the runtime.
The next time you notice in Task Manager that “memory is growing”, start by asking this question.
Is what is growing the Working Set, Private Bytes, Virtual Bytes, or System Commit?
This question alone makes the entry point of the investigation considerably more precise.
Related Articles
- Telling GC Latency From a Memory Leak in .NET — A Practical Procedure for Observing, Comparing, and Proving Growing Memory
- Process Explorer / Handle / VMMap in Practice — Tracking Hangs, Leaks, and “File in Use” From the State of This Very Moment
- Shared Memory Pitfalls and Best Practices — Designing Safe Inter-Process Sharing on Windows
- Lazy Writing in the Windows Cache Manager — From WriteFile to the Disk
- Why Windows Apps Crash After Long Runs Because of Handle Leaks, and What to Do About It
Related Consulting Areas
KomuraSoft LLC handles root-cause investigations of growing memory in Windows apps, performance degradation after long runs, OutOfMemory in 32-bit processes, and memory shortages that occur only in a customer’s environment, combining PerfMon, VMMap, RAMMap, WinDbg, and the .NET diagnostic tools. Rather than stopping at “memory is high”, we isolate which region grows, during which operation, why, and from where it is referenced and retained.
- Windows Application Development
- Bug Investigation and Root-Cause Analysis
- Technical Consulting and Design Review
- Contact Us
References
-
Microsoft Learn, Working Set. On a process’s Working Set being the set of pages currently resident in physical memory, including shared pages, the difference between soft and hard page faults, Transition pages, and the removal of pages from the Working Set. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, PROCESS_MEMORY_COUNTERS_EX2 structure. On the definitions of WorkingSetSize, PrivateWorkingSetSize, PrivateUsage, and SharedCommitUsage, and on PagefileUsage and PrivateUsage both representing the process’s Commit Charge. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Introduction to page files. On the page file supporting the removal of modified pages from RAM, system crash dumps, and the extension of the System Commit Limit, the definitions of System Commit Charge and Commit Limit, and their measurement in Task Manager and the performance counters. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Page State. On the Free, Reserved, and Committed states of a virtual page, and on Reserved pages having no physical storage associated with them and being inaccessible. ↩ ↩2
-
Microsoft Learn, VirtualAlloc function. On the difference between MEM_RESERVE and MEM_COMMIT, on a commit being charged against the system-wide memory and page files, and on actual physical pages possibly not being allocated until first access. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, How to determine the appropriate page file size for 64-bit versions of Windows. On page file size depending on peak Commit Charge and crash dump requirements, on the sources of hard page faults not being limited to the page file but including EXEs, DLLs, and memory-mapped files, and on the related performance counters. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Virtual Address Space. On each process having its own virtual address space and page tables, and on a virtual address not being the physical address itself. ↩
-
Microsoft Learn, Memory Limits for Windows and Windows Server Releases. On the user-mode virtual address space of a 32-bit process normally being 2GB, and being 2GB or 4GB on 64-bit Windows depending on whether IMAGE_FILE_LARGE_ADDRESS_AWARE is set. ↩
-
Microsoft Learn, SetProcessWorkingSetSize function. On the Working Set minimum and maximum not guaranteeing residency, on being able to empty the Working Set, and on excessive settings or operations being able to degrade system performance. ↩
-
Microsoft Learn, Memory Performance Information. On the correspondence among Windows performance counters, the memory management APIs, and the Task Manager display, including the Process object’s Working Set, Working Set - Private, and Private Bytes and the Memory object’s Committed Bytes and Commit Limit. ↩
-
Microsoft Learn, MapViewOfFile function. On Commit Charge being claimed at mapping time with
FILE_MAP_COPY, so that the entire view can be backed by the page file, because every page may become copy-on-write. ↩ ↩2 -
Microsoft Learn, Understanding Node Metrics and Properties in HPC Cluster Manager. On Available Physical Memory being calculated as the sum of the Zeroed, Free, and Standby lists, and on the meaning of each page list. ↩
-
Microsoft Sysinternals, RAMMap. On analyzing Windows physical memory usage by purpose, page list, process, priority, physical page, and file. ↩ ↩2
-
Microsoft Learn, Process.WorkingSet64 Property. On
WorkingSet64returning the process’s Working Set in bytes and corresponding to the Process object’s Working Set performance counter. ↩ -
Microsoft Learn, Process.PrivateMemorySize64 Property. On
PrivateMemorySize64returning the process-private memory that cannot be shared with other processes and corresponding to the Private Bytes performance counter. ↩ -
Microsoft Learn, Process.VirtualMemorySize64 Property. On
VirtualMemorySize64returning the amount of the process’s virtual memory and corresponding to the Virtual Bytes performance counter. ↩ -
Microsoft Sysinternals, VMMap. On breaking a process’s committed virtual memory down by type and displaying the physical memory (Working Set) assigned to each, together with a detailed memory map. ↩ ↩2
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
The Depths of Windows Memory (Part 2) — The Life of a Physical Page: Five Lists and the Truth About the Page File
Connects the PFN database, Standby, Modified, memory compression, and the page file to explain where a physical page goes after it leaves...
Time Travel Debugging — Recording and Rewinding the Bugs That Never Reproduce in Long-Running Apps
A once-a-month bug leaves only its result in a crash dump. Record and rewind execution with WinDbg Time Travel Debugging (TTD): TTD.exe, ...
Apps That Break on Resume from Sleep — How Power Events Work and How to Build Business Apps That Survive Resume
Why business apps break after a laptop resumes from sleep: WM_POWERBROADCAST notifications, Modern Standby, reconnect design, sleep suppr...
DllMain and the Loader Lock — The Real Reason You Are Told to "Do Nothing in DLL Initialization"
Why DllMain must not call LoadLibrary or wait on threads: the loader lock serializes DLL notifications, typical deadlocks, deferred initi...
What "Not Responding" Really Is — How Windows Decides an App Has Hung, and How to Design Apps That Don't
Windows marks a window Not Responding after 5 seconds without message retrieval and shows a ghost window: the check, hang causes, UI-thre...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Bug Investigation & Long-Run Failures
Topic page for intermittent failures, communication diagnosis, long-run crashes, and failure-path test foundations.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Bug Investigation & Root Cause Analysis
We investigate difficult production issues such as intermittent failures, long-run crashes, leaks, and communication stoppages.
Frequently Asked Questions
Common questions about the topic of this article.
- Is Task Manager's "Memory" column the total memory an app has allocated?
- No. Task Manager has several memory columns, including the Working Set family, the Private Working Set family, and Commit Size, and the meaning changes with the screen and column you look at. Working Set is the pages currently in RAM; Private Bytes or Commit Size is the commit that is private to that process. Do not reinterpret a single "Memory" column as the total an app has allocated or as the size of a leak.
- What is the difference between Working Set and Private Bytes?
- Working Set is the amount of pages visible to the process that are currently resident in physical RAM, and it includes shared pages such as DLLs and memory-mapped files. Private Bytes is the amount of committed memory used only by that process, regardless of whether it is currently resident in RAM. The two are therefore not the same value, and there is no simple ordering between them either.
- Does Task Manager's "Committed 18/32GB" mean 18GB has been written to the page file?
- No. The left-hand figure is the commit the whole system is currently promising to back, and the right-hand figure is the Commit Limit the system can support. The limit is determined roughly by RAM plus the page file, but the entire left-hand amount is not sitting in the page file. Many committed pages are in RAM, and some pages have never been assigned a physical page at all. Meanwhile, pages that can be reloaded from their original file, such as EXEs, DLLs, and memory-mapped files, do not necessarily raise private Commit by the same amount they raise Working Set.
- Can OutOfMemory occur even when free RAM is available?
- Yes. Allocation can fail for reasons other than physical RAM, such as a 32-bit process running out of virtual address space, a shortage of contiguous free address range, the system's Commit Limit, or limits specific to a Job Object or runtime. In particular, a 32-bit process on 64-bit Windows is normally capped at 2GB of user-mode virtual address space unless it is Large Address Aware.
- Does disabling the page file make Windows faster?
- In general, you cannot assume it will. Disabling the page file lowers the system's Commit Limit, makes it harder to move unused modified pages out of RAM, and affects the crash dump configuration. Page file size should be decided after measuring peak commit and the crash dumps you need; it is not a setting to disable without evidence.
- Does a high Page Faults/sec mean memory is short?
- You cannot tell from that alone. Page faults include soft faults, which can be resolved from Standby pages in RAM or pages shared with another process, and hard faults, which read from disk. Rather than looking at Page Faults/sec on its own, check Pages Input/sec, Page Reads/sec, Available MBytes, disk latency, and processing time on the same timeline.