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."