Paging¶
What paging is¶
Paging divides both virtual address spaces and physical memory into fixed-size blocks — pages (virtual) and frames (physical) — and maps any page to any frame through per-process page tables. The translation mechanics are in Virtual Memory. This page covers what paging means for memory management: why it replaced contiguous allocation, how the kernel decides which pages stay in RAM, what swapping and thrashing are, and how huge pages change the picture.
Why fixed-size pages¶
Before paging, a process needed one contiguous block of physical memory. As processes of different sizes came and went, free memory broke into gaps between them — external fragmentation — and a new process could fail to start with plenty of total free memory but no single gap large enough. Moving processes to compact memory was expensive.
With paging, any free frame can back any page, so external fragmentation of physical memory disappears. The cost is internal fragmentation: memory is handed out in whole pages, so a 4,097-byte need occupies two 4 KB pages, wasting 4,095 bytes. With 4 KB pages that waste is small; with 2 MB huge pages it can be large.
Physical memory is mostly cache¶
On a busy Linux server, physical memory holds four kinds of pages:
| Kind | Example | If the kernel needs the frame back |
|---|---|---|
| Anonymous | Heap, stacks, malloc arenas |
Must be written to swap first (if swap exists); otherwise cannot be reclaimed |
| File-backed, clean | Page cache of files read; program text | Can simply be dropped — the data is still on disk |
| File-backed, dirty | Page cache of files written but not yet flushed | Must be written back to the file first |
| Kernel | Slab caches, page tables, network buffers | Partly reclaimable (some caches can shrink) |
So "free memory" on Linux is usually small by design: unused RAM is
used as page cache.
The real measure of headroom is MemAvailable in /proc/meminfo —
free memory plus what can be reclaimed cheaply.
Page reclaim: how the kernel chooses victims¶
When free memory falls below a threshold (the low watermark), a kernel thread, kswapd, starts reclaiming pages in the background. If allocation outpaces it and memory falls below the min watermark, the allocating process itself must reclaim before it gets memory — direct reclaim, which shows up as sudden latency spikes in whatever thread happened to allocate.
Approximating LRU with accessed bits¶
Tracking exact LRU order on every memory access is impossible (it would need a list update per load). The hardware helps with the accessed bit in each page table entry: the MMU sets it on any access. The kernel periodically clears and checks these bits — the same idea as the CLOCK algorithm (see Cache Replacement Policies).
Linux keeps pages on active and inactive lists (separately for anonymous and file pages). New pages start inactive; a page accessed again while inactive is promoted to active; reclaim takes pages from the tail of the inactive list, and refills it by demoting not-recently-used active pages. This two-list design gives scan resistance: a large file read once fills the inactive file list and is reclaimed from there, without evicting the hot active pages. Since Linux 6.1, MGLRU (multi-generational LRU) refines this with several generations instead of two.
Anonymous vs. file pages: swappiness¶
vm.swappiness (0–200, default 60) biases reclaim between dropping
file pages and swapping out anonymous pages. Low values protect process
memory and sacrifice page cache; high values do the opposite. Database
servers often set it low (1–10), preferring to drop cached file data
over swapping out the database's own heap or buffer pool.
Swap¶
Swap is disk space used to hold anonymous pages evicted from RAM. When a swapped-out page is touched, a major page fault reads it back (see Page Faults).
| Device | Approximate cost of one swapped-in page |
|---|---|
| RAM access (for comparison) | ~100 ns |
| NVMe SSD | ~10–100 µs |
| HDD | ~5–10 ms |
Swap is not "more RAM": it is a place to put pages that are rarely
used, so their frames can serve something hotter. A small amount of
swap lets the kernel evict truly cold anonymous pages (initialization
code's data, leaked memory) instead of being forced to shrink the page
cache. A latency-sensitive database typically wants its hot memory
never swapped: some disable swap entirely, others lock critical
memory with mlock or rely on a low swappiness. zram (compressed
swap in RAM) is a middle ground common on desktops and phones.
Thrashing¶
When the combined working sets — the pages processes actively use — exceed physical memory, every page brought in evicts another page that will be needed again shortly. The system spends its time servicing page faults: CPU utilization drops (processes are blocked on I/O), disk I/O is saturated, and throughput collapses. That is thrashing.
How to recognize it on Linux:
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- ...
r b swpd free buff cache si so bi bo ...
1 12 812344 65536 1024 102400 4210 3988 18200 4100 ...
High si/so (pages swapped in/out per second) together with many
processes in b (blocked) is the signature. sar -B shows major
faults per second (majflt/s); PSI (pressure stall information,
/proc/pressure/memory) directly reports the percentage of time tasks
were stalled waiting for memory — the clearest signal of all.
The only real fixes are reducing the working set (smaller caches, fewer processes) or adding memory. Faster swap only softens the cliff.
Huge pages¶
A huge page maps 2 MB (or 1 GB) with a single page table entry at a higher level of the tree.
Benefits:
- TLB reach: one TLB entry covers 512x more memory. For a large, randomly accessed buffer pool, this removes most TLB misses.
- Fewer page table entries: a 64 GB buffer pool needs 16 million 4 KB PTEs (128 MB of page tables per process mapping it) but only 32,768 2 MB entries.
- Fewer page faults when first touching memory.
Costs:
- Internal fragmentation: a 2 MB page is allocated even if 4 KB is used.
- The kernel needs 2 MB of physically contiguous memory; after long uptime, physical memory may be too fragmented to find it.
Two ways to get them on Linux:
| Mechanism | How | Typical database advice |
|---|---|---|
| Explicit huge pages (hugetlbfs) | Reserve a pool at boot or via vm.nr_hugepages; applications request them (MAP_HUGETLB, SHM_HUGETLB) |
Recommended for large shared buffer pools (PostgreSQL huge_pages = on, Oracle SGA) |
| Transparent huge pages (THP) | The kernel automatically uses huge pages for suitable anonymous regions, and a background thread (khugepaged) collapses small pages into huge ones | Often disabled or set to madvise for databases (Redis, MongoDB, older PostgreSQL/Oracle guidance), because compaction to find contiguous memory and the collapse work can cause latency spikes, and copy-on-write of a 2 MB page after fork copies 2 MB instead of 4 KB |
Page tables cost memory too¶
Each process mapping memory needs page tables for it. For a database with many worker processes sharing a large buffer pool (the PostgreSQL model), each process builds its own page tables for the shared region as it touches it: 500 connections × 128 MB of 4 KB page tables for a 64 GB pool would be tens of gigabytes. Huge pages reduce this by 512x — one of the strongest practical reasons PostgreSQL deployments enable them. (Threads share one set of page tables, so thread-per-connection designs avoid the multiplication.)
Real-world usage: Redis is configured with
vm.overcommit_memory=1 on Linux so that fork() for background
snapshots succeeds even when apparent memory usage is near RAM capacity
— copy-on-write means the child will not actually use as much memory as
it appears to reserve. See Copy-on-Write.
Common pitfall¶
Diagnosing "high memory usage" without distinguishing its kinds. A
server with little free memory and a large page cache is healthy; one
with heavy swap-in activity is not, even if free shows similar
numbers. A process with a huge, sparse virtual address space (many
mmap regions) can carry significant page-table overhead unrelated to
the data it holds; a process whose RSS keeps growing may have a leak,
fragmentation, or simply a warming cache. Decide from rates and
pressure — major faults per second, swap-in rate, PSI memory stall
time — rather than from sizes, which cannot tell "using memory well"
from "running out of it."