Skip to the content
Software Made Clear Diagrams that show the mechanism About

Virtual memory

ANSWER

Every address your program uses is translated before it reaches the chip. Page tables map it to a physical address that can move, be shared with another process, or not exist yet — which is how two programs both believe they own address zero.

IN PLAIN TERMS

Every guest in the hotel is told they are staying in room one. The front desk keeps a book translating each guest's room one into a real room, and can move somebody overnight without telling them anything changed. Your program's addresses are looked up like that, every single time.

You met this phrase in an operating systems course, a job interview, or the moment a program died with an out-of-memory error, and the definition you found — an idealised abstraction of storage resources — told you nothing about what actually happens. Here is the part that got left out: the address inside your program is never the address your memory chip receives. Something sits between the two, translating one into the other on every access, and that something is what this page is about.

The address in your code is not an address#

Every pointer your program holds is a virtual address — a number meaningful only inside that process, not a location any chip recognises. Before the memory controller ever sees it, a piece of hardware called the memory management unit rewrites it into a physical address, consulting a data structure the kernel maintains called a page table. This translation is not optional and not skippable: it runs on every access a program makes, a read or a write alike, whether the program knows the mechanism exists or not.

The table does not map one address at a time. Addresses are grouped into fixed-size regions called pages, and the page table maps whole pages rather than individual bytes — because a table with an entry for every byte in an address space would dwarf the memory it exists to describe. Group addresses into pages instead, and the table shrinks to one entry per page, which is the only reason the whole scheme stays affordable. Fixed-size pages are not only a memory-system idea, either: a storage engine reads and writes pages of its own, on disk rather than in RAM, for reasons that belong to that layer, not this one.

Because each process gets its own page table, the same virtual address in two different processes can resolve to two entirely different pieces of physical memory. That is not a rule the kernel checks and enforces at the moment of access — it is a direct consequence of the mechanism: a process simply has no page-table entry that would let it name another process’s memory. Isolation exists in the sense that a locked door exists, not in the sense that a guard is standing there watching it.

Process Avirtual page 12Page table AAProcess Bvirtual page 12Page table BBPhysical memory
The same virtual page number in two processes lands on different physical memory, because each table is its own. The empty frames are the isolation — no entry points there, so nothing has to be checked.

A page-table entry does not have to point anywhere yet. A process can hold a virtual address that has never been backed by physical memory, and the moment it touches that address, the hardware raises a page fault: control passes to the kernel, which finds or creates the backing page, updates the table, and lets the faulting instruction run again — this time the translation resolves, and the program that touched the memory never itself sees an error. A page fault is a normal part of how memory gets backed on demand, not a symptom of something wrong. That is worth keeping cleanly separate from a segmentation fault, which is the kernel refusing a page-table entry that could never be made valid — an address outside anything the process was ever given, or a write to memory it was only allowed to read. One is the mechanism working exactly as designed. The other is the mechanism catching a program doing something it was never allowed to do.

What the translation buys#

Once every access already runs through a table, that table can be made to do more than translate. Four things fall out of it, each with a cost that comes attached.

What it buysWhat it makes possibleWhat it costs
IsolationA process cannot name another process’s memory, because no entry in its table points there.A translation on every access, even ones that never leave a single page.
SharingTwo processes’ page tables can point at the same physical page, so one copy of a library serves everyone running it.That shared page has to stay effectively read-only, or a write from one process would appear inside another’s supposedly private memory.
Lazy allocationA large allocation can succeed immediately, because it only has to reserve address space, not physical memory.A successful allocation becomes a promise about address space, not a guarantee the memory behind it exists yet.
RelocationThe kernel can move a process’s physical memory around while it keeps running, without the program’s own pointers ever changing.Addresses that look adjacent in your code no longer say anything about what is adjacent on the chip.

None of that is free, and the piece that keeps it affordable is a small cache of recent translations sitting inside the processor itself, usually called the translation lookaside buffer, or TLB. A translation that hits in that cache skips the page-table walk entirely; one that misses pays for the walk in full. What decides which happens is locality: a workload whose accesses stay inside a small set of pages keeps finding what it needs already cached, while one whose accesses scatter across many pages keeps missing and paying the walk’s full cost, over and over. Same table, same mechanism, a very different bill depending on how the program touches memory — and naming an actual size for that cache would mean naming a number that depends entirely on whichever processor happens to be running the code, so the direction of the effect is the honest claim to make, not a figure.

The crash lands somewhere else#

The failure that sends people looking in the wrong place starts with lazy allocation, the third row of that table. A program asks for a large buffer, gets a pointer back, and the allocation call reports success — because success only ever promised address space, and address space is cheap to reserve. Nothing about that pointer says the physical memory behind it exists yet.

The crash arrives later, somewhere else entirely: at the first write into a part of the buffer nobody has touched before, which is exactly the write that forces the kernel to back a fresh page. If the machine is genuinely out of memory at that moment, backing the page is not possible, and something has to give. On Linux, what happens next is configuration, not a fixed rule: the kernel can be set to overcommit memory in the first place — handing out address space against a promise it may not be able to keep — and when that promise comes due with nothing available, a part of the kernel usually called the out-of-memory killer picks a process and ends it outright, rather than handing the failing write back to the program as an error it could catch. Neither the overcommit setting nor the killer’s choice of victim has one behaviour that holds everywhere; both are configuration a Linux system carries, and neither has a value this page can state as the default.

Which means the stack trace in the crash report points at the write that happened to land first — often deep inside a library, in code that has nothing to do with why the buffer was too big to begin with. The function that gets blamed is the one that touched the memory, not the one that asked for it, and chasing that function is how people spend real time in the wrong part of the codebase.

One caveat: this exact behaviour is not universal. A system configured not to overcommit reports the failure at allocation time instead, exactly where intuition expects it. What stays true everywhere, and is the actual lesson underneath all of it, is narrower and more durable than either configuration: a successful allocation is a promise about address space, never a guarantee that the memory behind it exists. Reading it as anything stronger than that is the mistake, regardless of which operating system or which setting is running underneath the program.

Virtual memory sits inside the foundations pillar this site keeps for exactly this kind of mechanism — the material most people meet first and rarely see explained past the point where the textbook definition stops.

IF YOU REMEMBER ONE THING

An address in your code is a promise the hardware translates, not a location it already owns. A page table can map that address to physical memory, share it with another process, or leave it unbacked until the first touch — and a successful allocation only ever confirms the first part of that.

Questions people also ask

5 QUESTIONS
Is virtual memory the same as swap space?

No — swap is one thing the mechanism enables, not what it is. Virtual memory is the address translation itself: every access goes through a page table before it reaches physical memory. Swapping is a policy built on top of that, moving a page's backing out to disk when memory is scarce, so a system with swap turned off entirely still has virtual memory running underneath it.

What is a page fault?

It is the hardware noticing a touched virtual address has no physical memory behind it, and handing control to the kernel to fix that before your instruction runs again. Most page faults are routine — the ordinary way memory gets backed on first use — invisible to the program that caused them. A different event from a segmentation fault, which is the kernel refusing an access outright.

Why can a program allocate more memory than the machine has?

Because an allocation only has to reserve address space at first, not physical memory, so the request can succeed before anything backs it. Whether the system is allowed to promise more address space than it could actually back if every promise were called in at once — and what happens if it cannot keep that promise — is a configuration decision, not a fixed property of allocation itself.

What does the TLB do?

It caches recent virtual-to-physical translations inside the processor, so a repeated access to an address the program used a moment ago skips the full page-table walk. A workload whose accesses stay clustered keeps finding its translations already cached; one that scatters across many pages keeps missing, and pays the walk's full cost each time instead.

Does virtual memory make my program slower?

Every access pays for a translation, but a cache of recent translations covers the common case, so the cost most programs actually feel is small and depends far more on how their memory accesses are arranged than on translation existing at all. What the mechanism buys back — isolation, sharing, memory that only costs something once it is touched — is why the trade is made anyway.