A backend service contains 40 MiB of executable code and read-only data, reserves a 512 MiB heap range, and creates several thread stacks. Starting the service does not require the operating system to place every byte of those regions in RAM.
The service may use only a small request-handling path, a fraction of its heap, and the active ends of its stacks. Loading everything eagerly would spend startup time and physical memory on pages that might never be accessed.
Instead, the operating system can make the virtual regions valid while postponing physical residency until the process actually touches each page. This strategy is called demand paging.
Demand paging brings a virtual page into usable physical memory when an access first requires it, rather than loading every possible page in advance.
The first access can cause a page fault. The kernel supplies the page, updates the translation, and retries the instruction. Later accesses can use the resident mapping without repeating that fault.
Consider a program with 100 MiB of executable code and static data.
An eager-loading strategy would prepare all 100 MiB before the program begins useful execution:
This is simple, but it assumes every page deserves RAM immediately. A program can contain rarely used error handling, administrative commands, initialization paths for optional features, and data for code paths not taken during this run.
Demand paging reverses the default:
If the process touches only 18 MiB of the 100 MiB image, the remaining pages never need to become resident for that execution.
The two strategies trade different resources:
Modern systems are not always purely eager or purely demand-driven. The operating system can load some essential pages immediately, bring others in on demand, and anticipate nearby future accesses when doing so appears beneficial.
Demand paging depends on separating two questions:
A region can be valid at the operating-system level while one of its pages is nonresident.
The kernel's virtual-region metadata records that the complete range is meaningful and describes its permissions and backing. The page table records the hardware-visible state of individual pages.
For a nonresident valid page, the page-table state prevents ordinary hardware access. When an instruction references it, the processor raises a page fault. The kernel then consults the higher-level region description and recognizes that the address is valid but needs materialization.
Contrast this with an invalid address:
The page fault is the same entry mechanism. The kernel's region metadata determines whether recovery is allowed.
A useful simplified model gives a virtual page three broad states:
Unmapped means the page does not belong to a valid process region. An ordinary access is invalid.
Valid but nonresident means the region authorizes the access in principle, but no currently usable resident frame supplies the page. The kernel knows how the contents should be produced or recovered.
Resident means a page-table translation identifies a physical frame with the required permissions, so the MMU can complete normal accesses.
These are conceptual states rather than one universal set of page-table bits. Operating systems and processor architectures encode them differently. Some nonresident information lives in kernel data structures or in page-table entries that hardware treats as unusable.
The transition back from resident to nonresident allows the operating system to reclaim physical memory while preserving the virtual region. The contents must either be reproducible or saved somewhere before the frame can be reused.
Loading simulation...
Suppose a process has a valid nonresident page at virtual address range:
An instruction reads virtual address 0x7120.
The demand-paging path is:
The kernel first verifies the region and permissions. Demand paging must not turn an arbitrary pointer into valid memory.
It then determines what bytes the page should contain. The answer depends on the region:
The kernel obtains a physical frame. If no free frame is immediately available, it may need to reclaim one from other resident contents. The selection policy is separate from the demand-loading decision.
After the contents are ready, the kernel installs a page-table entry and returns from the exception. The processor retries the original instruction at 0x7120.
Anonymous memory has no file containing its initial bytes. Programs expect newly supplied anonymous memory to begin as zero.
Consider:
At the language level, every counter initially reads as zero. The operating system does not necessarily allocate and clear all corresponding private frames before calloc() returns.
A demand-zero strategy records that untouched pages should behave as zeros. On first use, the kernel can provide a zero-filled frame and install the mapping.
Some systems can satisfy initial reads using a shared read-only zero page and allocate a private frame only when the process writes. The exact optimization varies. The required behavior does not:
A process must never see data left behind by the previous owner of a physical frame.
Zeroing is therefore both a language/runtime expectation and a security boundary. A frame must be cleared before it becomes visible as new anonymous memory.
Demand-zero paging makes large sparse data structures practical. Reserving address space for a large array does not require physical frames for portions the program never touches.
An executable file already contains the initial bytes for program instructions and initialized static data. Those file ranges provide natural backing for demand paging.
At process startup, the loader can establish virtual regions that describe:
It does not need to read every file page before transferring control to the program's entry point.
When the CPU first fetches an instruction from a nonresident executable page:
If the bytes are already present in a system memory cache, the fault can be resolved without storage I/O. Otherwise, the faulting thread waits while the data is read.
Rarely executed code may never become resident. This is useful for large applications and shared libraries whose full functionality is not exercised by every process.
Writable initialized data also begins with file-derived bytes, but modifications must behave as private process state unless the region was explicitly designed for sharing. The page's backing and permissions determine how the kernel handles its first access and later writes.
A thread can receive a sizable virtual stack range while initially using only a small portion near its active stack pointer.
Only the resident pages consume physical memory. The growth area is reserved and valid but costs nothing until touched, and the guard area turns an overrun into a fault rather than silent corruption.
As function calls and local variables require more stack space, an access can enter a permitted growth area. The kernel supplies another zero-filled page and retries the instruction.
This does not mean a stack can grow without limit. The address must fit the operating system's stack-growth rules and configured resource limit. Crossing a guard boundary or accessing an implausibly distant address is invalid.
Lazy stack residency matters when a process has many threads. Reserving several megabytes of virtual stack range per thread does not necessarily consume the same amount of RAM immediately. Only the pages actually used need resident frames.
Application allocation and physical residency occur at different layers.
When malloc() returns a pointer, several things may be true:
The return of a non-null pointer proves that the allocator accepted the request under current policy. It does not prove that every byte has a dedicated resident frame.
Consider a 1 GiB allocation:
Immediately after the call:
After writing one byte per page:
This distinction explains why virtual-size metrics can increase before resident-set metrics. It also explains why an application can receive an allocation successfully and encounter memory-pressure consequences only as it touches more pages.
The exact behavior depends on the allocator, allocation size, operating-system policy, resource limits, and whether the memory was already resident.
Demand paging relies on the observation that most programs do not use all of their virtual memory equally at every moment.
Programs exhibit:
A request handler may repeatedly use the same code, stack pages, and a compact set of heap objects. Optional code and old data can remain untouched.
This creates an active set of pages that matters now. Demand paging lets physical memory concentrate on that active portion instead of the process's full theoretical layout.
The benefit appears in several forms:
The strategy is effective when a workload repeatedly uses a manageable set of pages. If it continually moves across more actively needed contents than physical memory can hold, repeated faults and data movement can overwhelm the benefit.
Pure demand paging would bring in only the page that caused the current fault.
That can underuse spatial locality. If a program reads a file sequentially, it is likely to need the next page soon. Waiting for a separate storage operation and fault for every page adds avoidable latency.
An operating system can perform read-ahead or prefetching:
If the prediction is correct, later accesses find the nearby contents already resident and avoid major faults.
If the prediction is wrong, the extra pages consume I/O bandwidth and physical memory without providing useful work.
The tradeoff is:
Practical systems combine demand with adaptive anticipation rather than following one fixed rule for every workload.
Read-ahead does not change the virtual-memory contract. Each virtual page still has its own mapping state. It changes when the kernel chooses to make nearby contents resident.
Demand paging moves work from allocation or startup time to first-access time.
For a new anonymous page, first write can require:
This normally produces a minor fault because no old contents must be read from storage.
For nonresident file or swapped contents, first access can additionally require storage I/O. The faulting thread blocks until the bytes are available, producing a major fault.
Later accesses to the resident page avoid this materialization path. This creates a cold-versus-warm difference:
A service can therefore show higher latency immediately after startup, deployment, or a large change in its active dataset. Warm-up traffic often exists partly to make important code and data resident before latency-sensitive requests arrive.
Warm-up has a cost: touching everything eagerly recreates many disadvantages of eager loading. A useful warm-up targets the pages likely to serve real traffic.
Even a low page-fault rate can matter because fault service is much slower than a normal memory access.
Let:
A simplified effective-access-time model is:
Assume:
Then:
One fault per million accesses adds roughly 1% in this simplified example.
If the average fault requires 10 ms instead:
The effective time becomes roughly 110 ns, a 10% increase despite the same low fault probability.
This classroom calculation spreads fault cost across accesses. Real applications experience latency in bursts: the specific request or thread that faults can pause for far longer than the average suggests. Tail latency can therefore worsen even when average throughput changes only slightly.
Demand paging operates at page granularity.
With 4 KiB pages, touching one byte can cause the system to make a 4 KiB unit resident:
If the program soon uses the neighboring bytes, the rest of the page is useful. If it touches one byte from each widely separated page, most resident bytes may remain unused.
Consider a 100 MiB virtual array:
A sequential first pass can encounter up to one first-touch event per page under a strict demand model, though operating-system optimizations can change the observed count.
Accessing only 100 bytes can still touch 100 different pages if the bytes are spread far apart. The useful payload is tiny, but the residency footprint can approach:
Layout and access pattern therefore affect both fault count and physical-memory efficiency.
Demand paging needs a frame for a newly resident page. If no free frame is immediately available, the operating system must make one available.
Conceptually:
The right branch is where page faults become expensive. Its cost depends on what has to be preserved before the frame can be reused.
Clean file-derived contents can often be discarded because the file can supply them again. Modified or anonymous contents require a way to preserve their state before the frame is reused.
Choosing which resident page should lose its frame is a policy problem. The demand-paging mechanism only establishes why a frame is needed and how the requested page becomes resident.
Reclaim introduces additional latency. If supplying one page repeatedly forces another soon-needed page out of memory, the system can spend increasing time moving contents instead of executing application code.
The following program records minor and major faults before allocation, after allocation, and after touching one byte per page:
Compile and run:
A typical result shows little change immediately after malloc() and a much larger minor-fault increase after the loop.
Exact counts vary because:
Run the program more than once and compare the pattern rather than expecting one exact count.
To see total resource usage as well:
The important observation is the separation between obtaining a virtual allocation and making its individual pages resident through access.
Demand paging changes how memory behavior appears in production.
Starting a process does not warm every code and data page. Initial requests can pay minor or major faults for paths that were not used during initialization.
A runtime can reserve a heap much larger than its current resident use. Capacity settings and virtual-size metrics should not be mistaken for current RAM consumption.
Large address ranges can be efficient when only a clustered subset is touched. Randomly touching a tiny amount from every page can create a much larger resident footprint and more translation work.
Major faults inside a request path make latency depend on storage. Minor-fault bursts can also be visible when many pages are first touched at once.
Targeted warm-up can move important code and data into resident memory before traffic arrives. Indiscriminate warm-up can waste the memory and startup work that demand paging was meant to save.
The operational question is not merely how much virtual memory a service has reserved. It is which pages the workload actively touches, whether they remain resident, and what cost is paid when they do not.
Demand paging lets the operating system establish valid virtual regions without immediately supplying resident physical frames for every page. First access to a valid nonresident page causes a fault; the kernel determines the page's backing, obtains a frame, supplies the required contents, updates the mapping, and retries the instruction.
Anonymous pages can be supplied as zeros, executable and file-derived pages can obtain their initial bytes from files or system caches, and thread stacks can gain resident pages as they grow. This reduces startup work and physical-memory use for untouched regions.
The tradeoff is first-access latency. Minor faults perform in-memory kernel work, while major faults wait for storage. Demand paging works best when workloads exhibit locality and repeatedly use a manageable active set of pages; sparse, unpredictable access can turn lazy loading into repeated fault overhead.
5 quizzes