JVM Memory Layout: Heap, Stack & Metaspace Deep Dive
Memory management in the Java Virtual Machine is designed to automate memory allocation and deallocation, freeing developers from manual pointer management (like malloc and free in C). However, building high-throughput enterprise applications requires a precise understanding of JVM runtime data areas, object headers on the heap, stack frame operand operations, and off-heap Metaspace tuning.
In this architecture deep dive, we examine JVM runtime memory regions, break down HotSpot 64-bit object headers, evaluate Thread-Local Allocation Buffers (TLAB), and analyze memory alignment padding.
1. Runtime Data Areas Architecture
When the JVM runs an application, it divides host memory into five distinct runtime data areas categorized into Thread-Private and Thread-Shared memory regions.
flowchart TD
subgraph SharedMemory["Thread-Shared Memory (Accessible by All Application Threads)"]
subgraph Heap["Java Heap Space (-Xms / -Xmx)"]
subgraph YoungGen["Young Generation"]
Eden["Eden Space (New Allocations)"]
S0["Survivor Space 0 (S0)"]
S1["Survivor Space 1 (S1)"]
end
OldGen["Old / Tenured Generation (Long-lived Objects)"]
end
Metaspace["Metaspace (Off-Heap Native Memory)\nClass Metadata, Constant Pool, Method Bytecodes"]
end
subgraph PrivateMemory["Thread-Private Memory (Per Thread Stack)"]
Stack["Thread Stack Frame (-Xss)\n(Local Variables, Operand Stack, Frame Data)"]
PC["Program Counter (PC) Register"]
NativeStack["Native Method Stack (JNI C-Calls)"]
end
Thread-Private Memory Regions
- Program Counter (PC) Register: Stores the memory address of the JVM bytecode instruction currently being executed by the thread.
- Native Method Stack: Stores stack frames for C/C++ native JNI function calls.
- Java Thread Stack (-Xss): Allocated per thread upon creation. Stores a series of Stack Frames corresponding to active method calls. Each stack frame contains:
- Local Variable Table (LVT): Stores primitive method arguments and object reference pointers.
- Operand Stack: Pushdown LIFO workspace for executing arithmetic and bytecode operations.
- Frame Data: Stores constant pool resolution references and method return addresses.
Thread-Shared Memory Regions
- Java Heap Space (-Xms / -Xmx): Stores all object instances and array allocations. Divided into Young Generation (Eden, S0, S1) and Old Generation.
- Metaspace (Native Memory): Replaced PermGen in Java 8. Located in off-heap OS native memory, storing class structures, method bytecodes, annotations, and constant pools. Configured via
-XX:MaxMetaspaceSize.
2. HotSpot 64-Bit Object Header Layout
Every object instance allocated on the Java Heap includes a hidden overhead header created by the HotSpot JVM. On a 64-bit JVM, an object header consists of two primary components: the Mark Word and the Klass Word.
block-beta
columns 1
mark["Mark Word (8 Bytes / 64 bits)\n(Identity HashCode, GC Age, Biased Lock Flag, Lock State Bits)"]
klass["Klass Pointer Word (4 Bytes with +UseCompressedClassPointers / 8 Bytes raw)\n(Pointer to Class Metadata in Metaspace)"]
data["Instance Fields Data\n(Primitive Values & Reference Pointers)"]
padding["Alignment Padding\n(0 to 7 Bytes to round object size to multiple of 8)"]
The Mark Word (8 Bytes)
The Mark Word is a multi-purpose header field whose interpretation changes dynamically based on the object's state using the bottom 2-3 lock bits:
- Unlocked State (001): Stores object identity hash code, GC age bits (4 bits, max age 15), and biased lock flag (0).
- Biased Lock State (101): Stores thread ID pointer of the locked owner thread.
- Lightweight Lock State (000): Stores pointer to basic object lock in the owner thread stack frame.
- Heavyweight Lock State (010): Stores pointer to native OS Object Monitor mutex.
- Marked for GC (011): Used during garbage collection sweep marking.
Memory Alignment Padding
The JVM requires all objects on the heap to begin at memory addresses that are multiples of 8 bytes. If an object's total size (Header + Fields) is 21 bytes, the JVM adds 3 bytes of zero padding to round the allocation to 24 bytes, maximizing CPU L1/L2 cache line alignment speed.
3. Fast Object Allocation: TLAB (Thread-Local Allocation Buffer)
Because the Java Heap is shared across all application threads, concurrent allocation in Eden space would require global lock synchronization, creating severe contention bottlenecks. HotSpot solves this using Thread-Local Allocation Buffers (TLAB).
flowchart LR
subgraph EdenSpace["Eden Space Memory Region"]
TLAB1["TLAB Buffer (Thread 1 - Lock Free Allocation)"]
TLAB2["TLAB Buffer (Thread 2 - Lock Free Allocation)"]
TLAB3["TLAB Buffer (Thread 3 - Lock Free Allocation)"]
end
Thread1["Thread 1 (new Order())"] --> TLAB1
Thread2["Thread 2 (new Customer())"] --> TLAB2
During thread initialization, the JVM assigns each thread a private dedicated region inside Eden space called a TLAB. When a thread executes new Object(), it allocates memory inside its own private TLAB via a simple bump-the-pointer operation in O(1) time without taking global locks. Only when a thread's TLAB becomes full does it synchronize with the JVM heap to request a new TLAB block.
- To examine ClassLoader subsystem loading, verification, and initialization, read JVM Architecture & ClassLoader Subsystem.
- To master Garbage Collection phases, G1GC regions, and ZGC load barriers, read Garbage Collection Mechanics & Collectors.
- To learn about JIT escape analysis and scalar replacement optimizations, read Just-In-Time (JIT) Compilation & Optimizations.