malloc(500GB)
// This does NOT allocate 1GB of physical RAM. It reserves
// 1GB of virtual address space. Pages only become real on first write.
char *p = malloc(1024 * 1024 * 1024);
assert(p != NULL); // passes on most Linux systems with default overcommit settings
p[0] = 1; // *this* is when things get interesting
// This does NOT allocate 1GB of physical RAM. It reserves
// 1GB of virtual address space. Pages only become real on first write.
char *p = malloc(1024 * 1024 * 1024);
assert(p != NULL); // passes on most Linux systems with default overcommit settings
p[0] = 1; // *this* is when things get interesting
// This does NOT allocate 1GB of physical RAM. It reserves
// 1GB of virtual address space. Pages only become real on first write.
char *p = malloc(1024 * 1024 * 1024);
assert(p != NULL); // passes on most Linux systems with default overcommit settings
p[0] = 1; // *this* is when things get interesting
MAP_ANONYMOUS
63 48 47 39 38 30 29 21 20 12 11 0
┌───────────┬───────┬───────┬───────┬───────┬──────────┐
│ (unused) │ PML4 │ PDP │ PD │ PT │ Offset │
└───────────┴───────┴───────┴───────┴───────┴──────────┘ 16 9 9 9 9 12
63 48 47 39 38 30 29 21 20 12 11 0
┌───────────┬───────┬───────┬───────┬───────┬──────────┐
│ (unused) │ PML4 │ PDP │ PD │ PT │ Offset │
└───────────┴───────┴───────┴───────┴───────┴──────────┘ 16 9 9 9 9 12
63 48 47 39 38 30 29 21 20 12 11 0
┌───────────┬───────┬───────┬───────┬───────┬──────────┐
│ (unused) │ PML4 │ PDP │ PD │ PT │ Offset │
└───────────┴───────┴───────┴───────┴───────┴──────────┘ 16 9 9 9 9 12
/var/log/kern.log
void infinite_recurse(int n) { char buffer[4096]; // touches a new stack page on each call buffer[0] = n; // force the page to be mapped infinite_recurse(n + 1); // → SIGSEGV on guard page after ~8000-16000 frames (default 8MB stack)
}
void infinite_recurse(int n) { char buffer[4096]; // touches a new stack page on each call buffer[0] = n; // force the page to be mapped infinite_recurse(n + 1); // → SIGSEGV on guard page after ~8000-16000 frames (default 8MB stack)
}
void infinite_recurse(int n) { char buffer[4096]; // touches a new stack page on each call buffer[0] = n; // force the page to be mapped infinite_recurse(n + 1); // → SIGSEGV on guard page after ~8000-16000 frames (default 8MB stack)
}
/proc/sys/vm/overcommit_memory
MADV_SEQUENTIAL
malloc_trim()
man 2 mlock
/proc/<pid>/maps
/proc/<pid>/smaps - Find a free physical frame (a 4KB chunk of RAM)
- Zero it out — this is a security requirement enforced by the kernel. Without it, you'd get physical memory that still contains another process's data. POSIX mandates zeroing for mmap with MAP_ANONYMOUS, but Linux zeroes all new pages regardless, because handing out stale memory is how you get information leaks.
- Write the mapping into the page table: "this virtual address → this physical frame"
- Flush the relevant address translation cache entry (the TLB — more on this in a moment)
- Return to the CPU, which re-executes the faulting instruction - /proc/sys/vm/overcommit_memory — set to 2 to disable overcommit entirely. malloc will return NULL when memory is actually exhausted. This is appropriate for databases and other programs that prefer a clean error to a sudden kill.
- mlock() / mlockall() — pin pages in RAM, prevent swapping.
- madvise() — tell the kernel how you plan to use a memory region. MADV_SEQUENTIAL lets it read-ahead pages. MADV_FREE tells it the pages are unused and can be reclaimed.
- malloc_trim() (glibc-specific) — tell the allocator to release unused heap memory back to the kernel. Useful for long-running services that allocate a lot and then don't.
- LD_PRELOAD swap — because allocators are userspace, you can swap them out entirely. Replace ptmalloc with jemalloc or mimalloc by setting LD_PRELOAD. Companies have done this to get 10–30% memory savings and significant throughput improvements with zero code changes. - Your code calls malloc(64).
- The allocator checks its free list. If a suitable chunk exists, it returns a pointer immediately. Done.
- If not, the allocator calls mmap() (or brk()) to request more virtual address space from the kernel.
- The kernel updates the virtual memory area (VMA) list for your process. No physical RAM involved yet.
- The allocator carves off 64 bytes and returns the pointer.
- Your code writes to that pointer for the first time.
- The MMU translates the virtual address. Page table says: valid range, no physical frame.
- CPU raises a page fault.
- Kernel page fault handler runs. Finds a free physical frame. Zeroes it. Updates page table. Flushes TLB entry.
- CPU re-executes the faulting instruction. The write completes.
- Your program continues, completely unaware that hardware exceptions and kernel code were involved. - man 2 mmap, man 2 brk — The kernel interfaces your allocator actually uses.
- man 2 mlock — How to pin memory in RAM if you need a guarantee.
- Understanding the Linux Virtual Memory Manager — Mel Gorman's book, available free online. The definitive deep dive into everything above and more.
- /proc/<pid>/maps and /proc/<pid>/smaps — Watch your own process's virtual memory regions in real time. smaps shows RSS (resident set size) per region — the difference between virtual and physical becomes very concrete very fast.
- What every programmer should know about memory — Ulrich Drepper's 2007 paper. Still the best single document on the full memory hierarchy.