Guide

Two programs can both use the address 0x7ffd1234 and refer to different bytes. Each process sees its own flat range of addresses, and the kernel translates those addresses into real RAM. That translation is virtual memory. It keeps programs private from each other and lets them reserve more memory than the machine physically has.

The big idea

Memory is handled in fixed-size pages, usually 4 KiB. For each process, the kernel keeps a page table that maps each virtual page to a physical frame in RAM. The CPU's memory management unit (MMU) walks this table on every access.

Think of each process as having its own floor plan with room numbers. A translation list, the page table, says which real room each numbered room is. The analogy breaks in one place: a real room is only assigned when it is first used.

Concept

Page: a fixed block of memory, usually 4 KiB. Frame: a block of real RAM of the same size. The page table maps pages to frames.

Walking through one translation

Pages are 4096 bytes, which is 2 to the power of 12. So the low 12 bits of an address are the offset inside the page.

Virtual address   = 0x0000_7f3a_1234_5678
Offset            = low 12 bits         = 0x678
Virtual page no.  = address >> 12       = 0x7f3a12345
Page table says   : page 0x7f3a12345 -> frame 0x1a2b3
Physical address  = (0x1a2b3 << 12) | 0x678
                  = 0x1a2b3000 | 0x678
                  = 0x1a2b3678

The offset passes through unchanged. Only the page number is translated.

The CPU caches recent translations in the TLB (translation lookaside buffer). With 4 KiB pages, 1,536 TLB entries cover 6 MiB. A program that jumps randomly through a 50 GiB in-memory index misses the TLB constantly. Huge pages (2 MiB or 1 GiB) help, because one entry then covers far more memory.

Lazy allocation and page faults

When malloc asks for memory, the kernel usually reserves addresses without assigning RAM. The first touch of a page triggers a page fault. The kernel finds a free frame, zeroes it, updates the table and resumes your instruction.

  • A minor fault is served from RAM and takes about a microsecond.
  • A major fault needs disk I/O, such as reading a memory-mapped file. It takes about 100 µs on NVMe to about 10 ms on a spinning disk.

This is why ps shows two memory numbers:

NumberMeaningUse it for
VSZ (virtual size)All reserved address spaceAlmost nothing. A JVM can show many GB here.
RSS (resident set size)Pages in RAM right nowWhat you pay for. Shared pages count in every process that maps them.

Overcommit and the OOM killer

Linux lets processes reserve more virtual memory than RAM plus swap, betting they won't touch it all. When the bet fails, the OOM killer (out-of-memory killer) picks a victim, usually the biggest process, and sends it SIGKILL.

Containers hit the same limit at a smaller scale. A container that passes its cgroup memory limit exits with code 137, and Kubernetes reports OOMKilled: true. The fix is usually a leak hunt or a correct limit, not a restart loop.

Copy-on-write

fork() would be slow if it copied a 2 GB address space. Instead, the child starts with page-table entries that point at the same frames, marked read-only. This is copy-on-write (COW). Only when one process writes to a page does the kernel copy that single 4 KiB page.

Redis uses this for snapshots. The forked child saves a frozen view of memory to disk while the parent keeps serving writes. If the parent rewrites 30% of pages during the snapshot, memory use grows by up to 30% of the dataset. That is why the Redis docs warn about spare memory.

Common mistakes

  • Alerting on VSZ. It is reserved space, not use. Alert on RSS or memory.current.
  • Assuming a fork copies all memory. It copies page tables only. Pages are copied one at a time, as they are written.
  • Ignoring TLB misses. Random access over a large structure can be slow even when the CPU is idle.

Check yourself

  1. Two processes both use address 0x7ffd1234. Why don't they clash? Each process has its own page table, so the same virtual address maps to different frames.
  2. A container exits with code 137 and OOMKilled: true. What should you check first? Its memory limit, and whether the process leaks memory.

Key takeaways

  • Each process sees a private address space. The page table maps 4 KiB pages to RAM frames.
  • Memory is assigned lazily. The first touch causes a page fault.
  • Use RSS, not VSZ, to judge memory use.
  • The OOM killer and cgroup limits kill processes that use too much. Exit code 137 is the usual sign.

Further learning

David Black-Schaffer's short lecture series explains page tables, TLBs and page faults better than most text. Start with the introduction.

Virtual Memory: 1 IntroductionDavid Black-Schaffer

Next, see how the kernel shares CPU time in CPU scheduling.