Virtual Address Space Layout (VMAs)
Every process in a modern OS like Linux (and thus Android) believes it has access to a massive, contiguous block of memory. This illusion is the virtual address space. The kernel manages the mapping between these virtual addresses and physical RAM pages using page tables.
A process's virtual memory is divided into contiguous ranges called Virtual Memory Areas (VMAs). Each VMA represents a distinct region with specific permissions (read, write, execute) and backing (anonymous or file-backed). The layout typically includes:
- Text Segment: Executable code (
.text). Read and Execute. - Data/BSS Segments: Global initialized and uninitialized variables.
- Heap: Dynamically allocated memory expanding upwards.
- Memory Mapping Segment: Area for
mmapcalls, shared libraries (.so), and large allocations. - Stack: Function call stack, expanding downwards.
You can inspect the VMA layout of an Android process using the adb shell:
adb shell cat /proc/<pid>/maps
Page Faults
When a thread attempts to access a virtual memory address, the CPU's Memory Management Unit (MMU) translates it to a physical address. If the page is not currently resident in physical RAM, or if the access violates the VMA permissions, the CPU triggers a hardware trap called a Page Fault.
- Minor Fault (Soft Fault): The page is present in physical memory but not currently mapped in the process's page table (e.g., shared library code already loaded by another app). The kernel quickly updates the page table.
- Major Fault (Hard Fault): The page is not in physical RAM. The kernel must suspend the thread, initiate disk I/O to read the page from storage, and resume the thread. This is a severe performance bottleneck.
Demand Paging
Demand paging is an optimization strategy where pages are only loaded into physical memory when a page fault actually occurs.
When Android launches an application, it does not copy the entire APK into RAM. It creates VMAs pointing to the ELF sections on disk. As the app executes and jumps to various functions, page faults trigger the kernel to load only the specific 4KB pages of code actually required. This guarantees incredibly fast process creation times and conserves physical RAM.
OOM and Overcommit Policy
Linux allows processes to allocate more virtual memory than there is available physical memory. This is called overcommit. It relies on the assumption that processes rarely use all the memory they request.
When processes actually try to use this memory and physical RAM is depleted, the system enters an Out of Memory (OOM) state.
Android handles OOM aggressively. The Linux kernel has an OOM Killer, but Android relies primarily on its custom user space daemon, lmkd (Low Memory Killer Daemon).
lmkd monitors system memory pressure via kernel events (like PSI: Pressure Stall Information). When memory gets critically low, lmkd terminates processes based on their oom_score_adj value. Background apps, cached empty processes, and services are killed first to protect foreground activities and core system services.
To check process memory states in AOSP:
adb shell dumpsys meminfo