How Linux and glibc Manage Memory: From Virtual Allocation to Physical Release
This article explains Linux's two‑level memory model, lazy physical allocation, the glibc malloc implementation, chunk metadata, large‑vs‑small allocations, memory holes, debugging hooks, leak detection with mtrace, and practical ways to measure a process's memory usage via /proc.
Linux Memory Model
Linux separates memory into a virtual linear address space and physical pages. When a program calls malloc or similar, the kernel only reserves a range of virtual addresses; actual physical pages are allocated lazily, i.e., on the first access to each page.
Lazy Physical Allocation Example
char *p = malloc(2048); // reserves 2048 bytes of virtual memory only
strcpy(p, "123"); // first write touches the page, causing the kernel to allocate a physical page (still 2048 bytes of virtual space)
free(p); // releases the physical page and the virtual regionMemory reservation and release are performed via system calls such as brk, sbrk, mmap and munmap. The process sees only virtual addresses; the kernel’s physical usage is transparent.
glibc Memory Allocator
glibc does not invoke a system call for every malloc / free. Instead it obtains a large chunk of virtual memory from the kernel:
For requests larger than 128 KB it uses mmap to map a separate region.
For smaller requests it expands the heap with brk / sbrk.
Subsequent allocations are satisfied from this internal pool, reducing the overhead of frequent system calls but introducing fragmentation.
Fragmentation and Heap Management
Irregular allocation sizes and frequent allocate/free cycles create holes. Small allocations (<128 KB) grow the heap upward using brk / sbrk. Large allocations are independent mmap regions, which do not guarantee upward growth. When the top of the heap contains a contiguous free block of at least 128 KB, glibc can shrink the heap by moving the break pointer, returning memory to the kernel.
Chunk Metadata
Each allocated block is preceded by a struct malloc_chunk that stores the block size. During free(p) the allocator reads the size at p‑4 (the metadata) and releases the block. glibc enforces a minimum allocation of 16 bytes to accommodate this metadata.
Debug Hooks and Leak Detection
glibc provides hook variables such as __malloc_hook and __malloc_initialize_hook that can be set to custom functions for tracing allocations. Memory‑leak detection is available via mtrace() and muntrace(). When MALLOC_TRACE points to a file, allocation and free events are logged; the supplied mtrace Perl script can analyse the log.
Example Program Using mtrace
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int *p, *q;
#ifdef DEBUGGING
mtrace();
#endif
p = malloc(sizeof(int));
q = malloc(sizeof(int));
printf("p = %p
q = %p
", p, q);
*p = 1;
*q = 2;
free(p);
/* q is intentionally not freed to demonstrate a leak */
return 0;
}Running the program with MALLOC_TRACE=trace.log produces a log that records the allocation of p and q. Because q is never freed, the log reveals a leak.
Measuring Process Memory via /proc
Linux exposes memory statistics through the /proc filesystem. Relevant files: /proc/meminfo – system‑wide memory usage. /proc/<pid>/maps – virtual address mappings of a process. /proc/<pid>/statm – memory usage of a specific process (size, resident, shared, etc.).
Example output of cat /proc/self/statm shows page counts for total virtual size, resident set, shared pages, and more.
Key Fields in /proc/<pid>/statm
Size (VmSize) – total virtual address space (pages).
Resident (VmRSS) – physical memory actually in use (pages).
Shared – shared pages.
Trs – executable virtual memory (VmExe).
Lrs – size of mapped libraries (VmLib).
Drs – data + stack size (VmData + VmStk).
The free command reports total, used, free, buffers and cached memory. Linux keeps memory in buffers/cache for performance; this memory can be reclaimed instantly, so “available” memory is free + buffers + cached.
Dynamic Linking and Memory Footprint
Executable files (ELF) contain separate code (read‑only, executable) and data (read‑write) segments. The loader maps these segments with mmap, initially only reserving virtual memory. Physical pages are allocated when the code or data is accessed.
Two ways to link libraries:
Link at compile time – the loader maps the library at program start; the library’s pages remain resident for the process lifetime.
Load at runtime with dlopen / dlclose – the library can be unmapped, freeing its physical memory.
Runtime loading is advantageous for long‑running processes that only occasionally need certain libraries.
Conclusion
Understanding Linux’s lazy allocation, glibc’s internal allocator, and the /proc interfaces enables developers to write memory‑efficient programs, diagnose leaks with mtrace, and accurately assess a process’s memory consumption.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
