Sign in
← Linux: memory
12 min · medium · Lesson 1 of 4

Virtual memory and pages

Why a process needs a virtual address space, and what pages, page tables, and the MMU are.

Why a process needs a virtual address space, what pages, page tables and the MMU are, and why "the process used 2 GB" in monitoring doesn't mean anything yet.

Why this matters

An alert saying "the service is using 1 GiB of memory" can be misleading: monitoring looks at the virtual size, while the process holds tens of times less physical memory. The reverse also happens: a process with a small virtual size can be eating the server's real memory. To tell one from the other, you need to understand how Linux hands out memory to processes.

A process does not see physical memory

Every process on Linux works with virtual memory: its own separate address space that looks contiguous. The physical RAM underneath it can be used by anyone and fragmented any way at all. The process cannot see any of that.

This abstraction solves two problems. The first is isolation. A process cannot read a neighbor's memory through an ordinary address access, because it simply has no addresses that point there. The second is convenience. A program does not need to know where its data physically sits in hardware.

The file /proc/<PID>/maps shows a process's address space map. /proc/self points to the reading process itself, so the command below shows the map of cat itself. The lines are filtered to show the program code and the stack:

grep -E 'bin/cat|stack' /proc/self/maps
55d3a1c00000-55d3a1c02000 r--p 00000000 08:02 393230  /usr/bin/cat
55d3a1c02000-55d3a1c07000 r-xp 00002000 08:02 393230  /usr/bin/cat
55d3a1c07000-55d3a1c0a000 r--p 00007000 08:02 393230  /usr/bin/cat
7ffd9c1f0000-7ffd9c211000 rw-p 00000000 00:00 0       [stack]

The first column is the address range, the second is the permissions (r read, w write, x execute, p private copy). Every address here is virtual. Between the code and the stack, a full listing also shows the heap ([heap]) and shared libraries like libc.so.6. Your addresses will be different: the kernel randomizes them on every run (ASLR).

Pages

Memory is sliced into pages. Allocation, accounting and eviction all happen in pages, not bytes. getconf shows the page size:

getconf PAGE_SIZE
4096

On x86_64 a page is always 4 KiB. On ARM64 the kernel can be built with 4, 16 or 64 KiB pages, so on those machines it is better to check the size than to assume it.

The mapping from a virtual page to a physical one lives in the page table. The table is organized hierarchically. The upper bits of the virtual address pick an entry at each level, and the bottom level holds the page's physical address. On x86_64 there are usually four levels, five on CPUs with 5-level paging.

Addresses are translated by the MMU (memory management unit), a hardware block in the CPU. To avoid walking the table on every access, recent translations are cached in the TLB (translation lookaside buffer). This is hardware, invisible to the program. The kernel fills in the page tables, and the MMU only reads them.

Memory is handed out lazily

The kernel keeps only what is actually needed in physical memory. This is demand paging: when a process asks for memory, the kernel at first only marks the address range as allocated. A physical page gets mapped in on the first real access to it. Such an access to a page that is not yet mapped in is called a page fault. The kernel handles it, and the process never notices.

So a process can "allocate" gigabytes and use almost nothing. By default the kernel allows allocating more than the free memory available (overcommit), betting that not everyone will touch it all at once.

The gap between allocated and actually used is visible in ps:

ps -o pid,vsz,rss,comm -p 812
  PID    VSZ   RSS COMMAND
  812 720160 98400 nginx

Replace 812 with your own process's PID.

VSZ (virtual memory size) is the size of the virtual address space in KiB. RSS (resident set size) is how much physical memory the process holds right now, also in KiB. VSZ is almost always far larger than RSS, and that is normal.

The same numbers without ps live in /proc/<PID>/status: the VmSize line matches VSZ, the VmRSS line matches RSS. This comes in handy in containers that have no ps:

grep -E 'VmSize|VmRSS' /proc/self/status
VmSize:     2848 kB
VmRSS:      1024 kB

The numbers depend on your system. What matters here is the ratio.

Common mistakes

Treating VSZ as memory usage. The process reserved addresses, but there may be no physical pages behind them. RSS (VmRSS) shows the real load on memory.

Adding up RSS across processes. RSS includes shared pages too, such as libc code loaded once for the whole system. Adding up the RSS of twenty workers from one service gives a sum larger than the real cost.

Expecting free memory to drop by 1 GiB right after a 1 GiB malloc. Memory is only taken for the pages the process actually writes to.

Assuming a page is always 4 KiB. True for x86_64, depends on the kernel for ARM64. When converting memory to pages, use the value from getconf PAGE_SIZE.

What you'll need for the tasks

The next lesson covers anonymous memory, the page cache, and why free shows little free memory.

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson linux-memory-01-virtual