You just bought a game that needs 32 GB of memory. Your laptop has
16 GB of RAM. You click play — and it runs. How? The game thinks it
owns a vast, private, continuous stretch of memory addresses from
The trick that makes it work is paging. Instead of handing each program one giant
contiguous block of real memory (which quickly leaves the free space in useless slivers — the
Pick a page size — a very common choice is 4 KB (that's
Because every page and every frame is the same fixed size, a page slots into any free frame with no gaps left over. That is the death of external fragmentation: free RAM is just a pile of interchangeable frames, and there is always room if any frame is free at all.
Notice how scattered the frames are — page 0 landed in frame 3, page 1 in frame 0, page 2 in frame 5. The program has no idea and no reason to care: it sees a tidy run of virtual pages, and the page table quietly untangles the scramble underneath.
Every memory access your program makes uses a virtual address, and the hardware must translate it to a physical address before touching RAM. With fixed-size pages this is beautifully mechanical. Split the virtual address into two parts:
With a 4 KB page the low 12 bits are the offset (which byte
within the page,
Worked example. Say the program reads virtual address
The offset never changes — a byte 20 places into page 5 is still 20 places into frame 9. Translation only ever rewrites the page number part into a frame number; the offset rides along untouched.
There's a catch. If every memory access needs a page-table lookup, and the page table itself lives in RAM, then each access to memory has secretly become two accesses to memory — one to read the page table, one to read the data. That would roughly halve speed.
The fix is a tiny, blisteringly fast hardware cache called the Translation Lookaside Buffer (TLB). It remembers the last handful of page → frame translations. On each access:
Because real programs touch the same few pages over and over (this clustering is called locality), the TLB hit rate is typically well over 95%, and the two-lookup penalty almost never actually happens.
No — and mixing these up is the classic beginner slip. They are two different caches failing at two different levels:
Every page fault involves finding the page absent, but a TLB miss on its own is cheap and routine. Don't conflate them.
Here is the payoff for the 32 GB-on-16 GB machine. A page table entry can be marked not present — the page exists in the program's virtual space but currently lives on disk, not in any frame. When the program touches such a page, the hardware raises a page fault, and the operating system steps in:
This is demand paging: a page is only ever brought into RAM the moment it's actually needed. A program far larger than physical memory runs fine as long as the pages it's using right now fit — the rest doze on disk. RAM becomes a cache for the disk.
When RAM is full and a new page must come in, the OS must throw one resident page out. The rule it uses is a page-replacement policy, and the goal is to minimise future page faults. Two classic policies:
Let's count faults for real. Below is a FIFO simulator over a fixed reference string (the sequence of pages a program touches) with a small number of frames. It prints HIT or FAULT for every access — showing which page gets evicted — and the total fault count at the end. Change the reference string or the frame count and press Run:
Try dropping numFrames to 2, or setting it to 4 — more frames should mean fewer faults.
Should. Read the next box before you bet on it.
You'd assume giving a process extra frames can only help — surely more RAM never hurts. Astonishingly, under FIFO it sometimes does. There exist reference strings where increasing the number of frames increases the number of page faults. This counter-intuitive result is called Bélády's anomaly.
A famous witness is the reference string
numFrames = 3 then 4) and watch it happen.
The lesson: FIFO doesn't track usefulness, only age, so more frames can reshuffle evictions for the worse. LRU (and other "stack" policies) provably never suffer this — for them, more frames can only help.
Page size is a trade-off. Tiny pages waste almost no space at the end of the last page (little internal fragmentation) but need an enormous page table with an entry per page — and more page faults to stream a program in. Huge pages shrink the page table and let one fault load a lot, but every allocation rounds up to a whole page, wasting memory inside it. Around 4 KB has been the sweet spot for decades, though modern systems also offer optional 2 MB and 1 GB "huge pages" for memory-hungry programs.