Virtual Memory Concepts
Larger Than Physical Memory
Read aloud is unavailable in this browser.
Breaking the Physical Barrier
In 1968, programmers at MIT developed the first commercial time-sharing system on a machine with 256 KB of memory—yet each user's program could address far more memory than physically existed. This seeming paradox wasn't magic; it was the birth of virtual memory's most transformative capability: allowing programs to use more memory than the machine physically contains.
Today, this capability is so fundamental that we take it for granted. Your laptop with 16 GB of RAM routinely runs applications whose combined memory demands exceed 100 GB. Video editing software opens 8K footage files larger than physical memory. Databases hold indexes that dwarf available RAM. Scientific simulations work with datasets measured in terabytes.
How is this possible? How can software use memory that doesn't exist? This page answers that question, exploring the mechanisms that break the physical memory barrier and the profound implications for system design.
By the end of this page, you will understand how virtual address spaces can exceed physical memory size, the role of secondary storage in extending memory capacity, the key mechanisms that make this possible, and the fundamental tradeoffs involved.
The Apparent Contradiction
At first glance, using more memory than exists seems impossible. Where can data go if there's no physical storage for it? The apparent contradiction dissolves when we realize that not all data needs to be in physical memory simultaneously.
The key insight: Locality of Reference
Programs don't access all their data uniformly. At any given moment, they're focused on a small subset of their total address space—the working set. This behavior, called locality of reference, has two forms:
- Temporal locality — Recently accessed locations are likely to be accessed again soon
- Spatial locality — Locations near recently accessed ones are likely to be accessed soon
Because of locality, we only need the currently active portions of a program in physical memory. Everything else can wait on disk until it's needed.
| Scenario | Total Data Size | Active Working Set | Locality Ratio |
|---|---|---|---|
| Text editor with large file | 500 MB file | ~1 MB visible + buffers | 1:500 |
| Web browser with many tabs | 2 GB total | 50 MB active tab | 1:40 |
| Database query execution | 10 TB database | 100 MB hot data | 1:100,000 |
| Compiling large project | 1 GB source | 10 MB current unit | 1:100 |
| Video editing timeline | 50 GB footage | 200 MB visible segment | 1:250 |
The enabling abstraction:
Virtual memory exploits locality by creating a two-level memory hierarchy:
- Primary level (Physical RAM) — Fast but limited; holds the working set
- Secondary level (Disk/SSD) — Slow but vast; holds everything else
The operating system automatically migrates data between these levels, keeping hot data in RAM and cold data on disk. From the program's perspective, it appears to have unlimited fast memory—the abstraction hides the underlying reality.
RAM access takes ~100 nanoseconds. SSD access takes ~100 microseconds. HDD access takes ~10 milliseconds. That's a 100x gap between RAM and SSD, and 100,000x between RAM and HDD. Virtual memory's success depends on minimizing trips to the slower levels.
How It Works: The Basic Mechanism
The mechanism that enables virtual address spaces larger than physical memory involves several cooperating components working together.
The core concept: Partial Residency
At any moment, only a fraction of a process's virtual pages are 'resident' in physical memory. The rest exist only in the backing store (swap space on disk). When a process accesses a non-resident page, a page fault occurs, triggering the OS to load the page from disk.
Step-by-step mechanism:
Memory Access with Virtual Memory Larger Than Physical:═══════════════════════════════════════════════════════ 1. INITIAL STATE: ┌──────────────────────────────────────────────────────────┐ │ Process Virtual Address Space: 16 GB │ │ Physical RAM: 4 GB │ │ Swap Space on Disk: 20 GB │ │ │ │ Currently: 3 GB of pages in RAM, 13 GB on disk │ └──────────────────────────────────────────────────────────┘ 2. PROCESS ACCESSES VIRTUAL ADDRESS 0x7FF000001000: ┌──────────────────────────────────────────────────────────┐ │ CPU checks page table for this virtual page... │ │ │ │ Page Table Entry says: │ │ Present bit = 0 (not in physical memory!) │ │ Disk location = Swap Block #4521 │ └──────────────────────────────────────────────────────────┘ 3. PAGE FAULT OCCURS: ┌──────────────────────────────────────────────────────────┐ │ CPU raises Page Fault Exception │ │ Control transfers to OS page fault handler │ │ │ │ The process is BLOCKED until fault is resolved │ └──────────────────────────────────────────────────────────┘ 4. OS HANDLING: ┌──────────────────────────────────────────────────────────┐ │ a) Find a free physical frame │ │ → If none free, evict a page (page replacement) │ │ │ │ b) Read page from Swap Block #4521 into frame │ │ → This takes ~100 μs (SSD) or ~10 ms (HDD) │ │ │ │ c) Update page table entry: │ │ → Present bit = 1 │ │ → Frame number = physical frame allocated │ │ │ │ d) Resume the faulting instruction │ └──────────────────────────────────────────────────────────┘ 5. ACCESS COMPLETES: ┌──────────────────────────────────────────────────────────┐ │ The instruction that faulted re-executes │ │ This time, TLB/page table has valid mapping │ │ Memory access succeeds! │ │ │ │ Now: 3 GB + 1 page in RAM, or if eviction occurred, │ │ still ~3 GB but with different pages │ └──────────────────────────────────────────────────────────┘The beauty of this mechanism is that the process doesn't know anything happened. It issued a memory access, waited (blocked), and received its data. From the program's perspective, it's just using memory—slowly for some accesses, quickly for others, but always successfully.
The Mathematics of Memory Over-commitment
Let's examine the quantitative aspects of having virtual address spaces exceed physical memory. Understanding these numbers helps in system sizing and performance tuning.
Virtual address space vs. allocated memory vs. resident memory:
Three different measurements describe a process's memory usage:
| Metric | Definition | Example Values | Where It Lives |
|---|---|---|---|
| Virtual Size (VSZ) | Total address space mapped | 100 GB | Exists in page tables |
| Resident Set Size (RSS) | Pages currently in RAM | 500 MB | Physical memory |
| Swap Usage | Pages currently on disk | 200 MB | Swap partition/file |
| Private Memory | Not shared with other processes | 300 MB | RAM + Swap |
| Shared Memory | Mapped libraries, shared segments | 400 MB | RAM (counted once) |
Over-commitment ratios:
Systems routinely over-commit memory—promising more virtual memory than physical memory exists:
Over-commitment Ratio = Total Virtual Memory Promised / Physical RAM Available
Example:
- 50 processes, each with 8 GB virtual space = 400 GB
- Physical RAM = 32 GB
- Over-commitment ratio = 400 / 32 = 12.5x
This works because:
- Processes don't use their entire virtual space
- Shared libraries are counted once physically but appear in each process's virtual space
- Locality means most pages aren't needed simultaneously
- Memory-mapped files can be re-read from their original file (not swap)
The OOM (Out of Memory) risk:
Over-commitment has a failure mode: if processes collectively try to use more memory than RAM + swap, the system runs out. This triggers the OOM Killer on Linux, which terminates processes to free memory—a traumatic but necessary response to over-commitment failure.
123456789101112131415161718
# View system-wide memory statistics$ free -h total used free shared buff/cache availableMem: 31Gi 8.5Gi 5.2Gi 1.2Gi 17Gi 21GiSwap: 16Gi 0.5Gi 16Gi # Check over-commit settings (Linux-specific)$ cat /proc/sys/vm/overcommit_memory0 # 0=heuristic, 1=always, 2=never over-commit $ cat /proc/sys/vm/overcommit_ratio50 # When mode=2, allow RAM + 50% of RAM as virtual # View a process's memory breakdown$ cat /proc/self/status | grep -E "(VmSize|VmRSS|VmSwap)"VmSize: 123456 kB # Virtual address space sizeVmRSS: 45678 kB # Resident set (in RAM)VmSwap: 1234 kB # Currently swapped outMemory over-commitment works when locality holds—when processes don't simultaneously access their full allocations. Workloads with poor locality (random access patterns, in-memory databases) may defeat this assumption and require careful memory sizing.
Backing Store and Swap Space
The backing store is the secondary storage that holds pages not currently in physical memory. Without it, virtual memory larger than physical memory would be impossible.
Types of backing store:
| Type | Contents | Writeback Required? | Examples |
|---|---|---|---|
| Swap Space | Anonymous pages (heap, stack) | Yes, if modified | swap partition, pagefile.sys |
| Executable Files | Code pages (text segment) | No (read-only) | ELF binary, PE executable |
| Shared Libraries | Library code and data | No (read-only code) | libc.so, kernel32.dll |
| Memory-Mapped Files | File contents mapped to memory | Yes, if mmap'd writeable | Database files, config files |
Swap space design:
Swap space is dedicated storage for pages that have no other backing file (anonymous pages). Design considerations include:
1. Swap partition vs. swap file:
- Partition: Slightly faster, dedicated space, harder to resize
- File: Flexible size, can be added/removed dynamically, slight overhead
2. Sizing guidance:
- Historical rule: swap = 2× RAM (from memory-scarce era)
- Modern practice: swap = RAM or less for desktops; depends on workload for servers
- Hibernation: swap ≥ RAM (to store entire memory image)
3. SSD considerations:
- SSDs make swap much faster (~100× vs HDD)
- Write wear is a concern for heavy swapping
- Many modern systems rely more on memory compression than swap
Where Pages Come From When Faulted:════════════════════════════════════ Page Type → Backed By → Example─────────────────────────────────────────────────────────────────Code page → Executable file → main() from /usr/bin/progLibrary code page → Shared library file → printf() from /lib/libc.soInitialized data → Executable file → const char* msg = "Hello"Heap page (new alloc) → Zero-filled on demand → malloc(1000)Heap page (swapped out) → Swap space → Previously used memoryStack page (new) → Zero-filled on demand → Growing stack frameStack page (swapped) → Swap space → Cold stack framesmmap'd file page → Original file → Database pagemmap'd anonymous → Swap space → Large allocation KEY INSIGHT:─────────────Pages backed by files don't need swap space—they can bere-read from their original file. Only "anonymous" pages(heap, stack, private data modified after loading) requireswap space for storage.A clean (unmodified) page backed by a file can be discarded without writing anywhere—if needed again, it's re-read from the file. This is why read-only code and data segments are 'cheap' in memory terms: they can be evicted and restored at will.
Demand Paging: Load Pages Only When Needed
Demand paging is the mechanism that enables virtual spaces larger than physical memory. Instead of loading an entire program into memory at startup, the OS loads pages only when they're accessed—'on demand.'
Why demand paging is essential:
Without demand paging, a program would need all its pages loaded before execution. Consider:
- A modern browser binary: 200+ MB
- Linked libraries: 500+ MB
- Data files: potentially GB
- Total: Would require gigabytes of free RAM before starting
With demand paging:
- Process starts with almost no pages loaded (or just a few critical ones)
- Execution begins immediately
- Pages are faulted in as accessed
- Many pages may never be loaded (unused features, error handlers)
- •Load entire program at startup
- •Startup blocked until all pages loaded
- •Unused code wastes memory
- •Large programs need large free RAM
- •Multiple large programs can't run
- •Load pages as accessed
- •Near-instant startup
- •Only used code occupies memory
- •Programs larger than RAM can run
- •Many large programs can coexist
The page fault as the loading trigger:
When a program references a page not yet loaded:
- The page table entry has Present=0 (not in RAM)
- CPU raises a page fault exception
- OS page fault handler determines the page should be loaded (vs. a genuine error)
- OS reads the page from its backing store into a physical frame
- Page table is updated with the frame number and Present=1
- Faulting instruction is restarted
This is invisible to the program. The load instruction sees a brief delay, then gets its data. The program doesn't explicitly request page loading—it just uses addresses, and the system provides the data.
Pure demand paging loads zero pages at startup—even the first instruction faults. Most real systems use 'prepaging' to load a few initial pages, reducing startup faults. Some also speculatively load pages adjacent to faulted ones (clustered paging), exploiting spatial locality.
Benefits of Exceeding Physical Memory
Having virtual address spaces exceed physical memory isn't just a trick—it fundamentally changes what's possible in computing. Let's enumerate the benefits:
- •Run programs larger than RAM — A video editor can work with 100 GB timelines on a 16 GB machine, because only visible/active portions need to be resident.
- •Run more programs concurrently — Twenty programs each claiming 4 GB can coexist on an 8 GB system, as long as their combined working sets fit in RAM.
- •Instant program startup — Programs begin executing immediately; pages load as needed rather than forcing the user to wait for complete loading.
- •Efficient memory utilization — Physical memory holds only actively-used data, not idle code paths or rarely-accessed data structures.
- •Simplified programming — Programmers can allocate large arrays and data structures without worrying about physical memory limits; the OS handles the complexity.
- •Graceful performance degradation — When memory pressure increases, performance degrades gradually (more page faults) rather than causing immediate failure.
The economic argument:
Virtual memory larger than physical has significant economic implications:
| Approach | Cost | Experience |
|---|---|---|
| Buy RAM to match peak usage | Very expensive | Always fast |
| Use virtual memory | Moderate | Usually fast, occasionally slow |
| Refuse to run large programs | Cheap | Frustrating |
Virtual memory finds the sweet spot: it enables capabilities that would otherwise require expensive hardware, accepting occasional slowdowns when working sets exceed RAM.
The software architecture impact:
Programmers can design software as if memory were unlimited:
- Databases can index huge datasets in 'memory' (actually virtual memory)
- Applications can memory-map large files for convenient access
- Scientific software can work with datasets larger than any machine's RAM
- Caches can be sized generously without counting every byte
While virtual memory enables using 'more' memory, heavy reliance on swap devastates performance. The disk-to-RAM speed gap means a swapping-heavy workload might run 1000× slower than one that fits in RAM. Virtual memory is a safety net and capability enabler, not a substitute for adequate RAM.
Tradeoffs and Limitations
The ability to exceed physical memory isn't free—it comes with significant tradeoffs that system designers and performance engineers must understand.
The fundamental tradeoff: Space vs. Time
| Storage Level | Latency | Relative Speed | Impact on Page Fault |
|---|---|---|---|
| L1 Cache | ~1 ns | 1× | N/A |
| L3 Cache | ~20 ns | 20× | N/A |
| RAM | ~100 ns | 100× | No page fault |
| NVMe SSD | ~100 μs | 100,000× | Causes ~0.1ms fault |
| SATA SSD | ~200 μs | 200,000× | Causes ~0.2ms fault |
| HDD | ~10 ms | 10,000,000× | Causes ~10ms fault |
When virtual > physical fails:
Thrashing — When the working set exceeds physical memory, the system spends most of its time swapping pages in and out rather than doing useful work. CPU utilization drops even as the system is 100% busy doing I/O.
OOM conditions — If swap space is exhausted and memory is still needed, the OS must kill processes. This is non-deterministic and can kill the wrong process.
Latency-sensitive workloads — Real-time systems, low-latency trading, interactive applications—page faults introduce unacceptable jitter.
Why locality can fail:
- Random access patterns (graph traversal, hash table with random keys)
- Working set grows suddenly (batch job processing large dataset)
- Many concurrent processes with overlapping working sets
- Memory pressure from other processes
1234567891011121314151617181920
Signs of Thrashing:════════════════════ # High page fault rate$ vmstat 1procs -----------memory---------- ---swap-- -----io---- r b swpd free buff cache si so bi bo 1 15 102400 50000 1000 10000 500 600 50000 60000 1 14 103000 48000 1000 10000 550 700 55000 70000 ▲ ▲ ▲ ▲▲▲▲▲ │ │ │ High I/O = thrashing │ │ └─ swap out │ └─ swap in └─ blocked processes (waiting for I/O) Key indicators: • High 'si' (swap in) and 'so' (swap out) • Many blocked processes ('b' column) • Low CPU utilization in 'top' despite high load • System feels slow/unresponsiveThrashing is self-reinforcing: as the system swaps pages out, those pages soon need to be swapped back in, causing more evictions. The only escapes are reducing memory pressure (killing processes), adding RAM, or using techniques like working set management that we'll cover in later chapters.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.