JVM Memory Structure: Architecture and Runtime Dynamics
The JVM memory structure is the bedrock of Java's architecture. Mastering its regions, allocation strategies, and reclamation cycles is the prerequisite for understanding GC mechanics, troubleshooting OOM errors, and performing high-level performance tuning.
1. Runtime Data Area Overview
JVM divides its managed memory into five primary regions, categorizing them by accessibility (Thread-Shared vs. Thread-Private).
graph TD
subgraph Shared["Thread-Shared (Global)"]
Heap[Heap - Objects & Arrays]
Metaspace[Metaspace - Class Metadata, Constants, Statics]
end
subgraph Private["Thread-Private (Per-Thread)"]
VMStack[VM Stack - Java Method Frames]
NativeStack[Native Stack - C/C++ Native Methods]
PC[PC Register - Instruction Pointer]
end
2. Thread-Private Regions
2.1 PC Register (Program Counter)
The smallest memory region. It records the address of the bytecode instruction currently being executed.
- Thread-Private: Each thread needs to know its own location to resume after a context switch.
- No OOM: It is the only area that never throws
OutOfMemoryError.
2.2 VM Stack (Java Virtual Machine Stack)
Describes the execution of Java methods. Every method call creates a Stack Frame.
- Stack Frame Components:
- Local Variable Table: Stores parameters and local variables (Primitives & References).
- Operand Stack: The "Worktable" where calculations actually happen (push/pop).
- Dynamic Linking: Points to the method reference in the Runtime Constant Pool.
- Return Address: Where to resume after method completion.
- Stack Errors:
StackOverflowError: Stack depth exceeded (e.g., infinite recursion).OutOfMemoryError: Failed dynamic expansion due to memory exhaustion.
3. The Heap: The Garbage Collection Battleground
The Heap is the largest region, storing nearly all object instances. It is managed by the Garbage Collector (GC) using a Generational Structure.
3.1 Young Generation (1/3 of Heap)
- Eden: Where new objects are initially born (approx. 80% of Young Gen).
- Survivor Spaces (S0 & S1): Objects that survive a Minor GC are copied here. They swap roles (
fromandto) in every cycle.
3.2 Old Generation (2/3 of Heap)
Stores long-lived objects. Objects are promoted to the Old Gen if:
- Age Threshold: They survive N Minor GCs (Default: 15).
- Size Threshold: Extremely large objects bypass the Young Gen to avoid copying costs.
- Dynamic Age Logic: If objects of a certain age occupy >50% of Survivor space, they are promoted early.
Best Practice: Set
-Xms(Initial Heap) and-Xmx(Max Heap) to the same value to prevent performance jitters caused by frequent heap resizing.
4. Metaspace: Moving to Native Memory
In JDK 8+, the Permanent Generation (PermGen) was replaced by Metaspace.
- Location: Moved from the JVM Heap to Native Memory (OS-managed).
- Rationale: PermGen had a fixed size leading to frequent
java.lang.OutOfMemoryError: PermGen space. Metaspace scales dynamically based on available system memory.
5. Direct Memory (Off-Heap)
Direct Memory is not part of the standard Runtime Data Area. It is allocated directly from the OS via NIO (ByteBuffer.allocateDirect()).
- Zero-Copy: It avoids redundant data copying between the OS Kernel space and the Java Heap.
- Use Case: Ideal for high-throughput I/O intensive middleware like Netty, Kafka, and RocketMQ.
6. Object Memory Layout
A Java object in memory consists of three parts:
- Object Header:
- Mark Word: 8 bytes (on 64-bit) containing HashCode, GC age, and Lock status.
- Klass Pointer: Points to the class metadata in Metaspace.
- Instance Data: The actual field values.
- Padding: Aligns the object size to a multiple of 8 bytes for CPU efficiency.
Compressed Oops and the 32GB Frontier
On 64-bit systems, pointers expand to 8 bytes, which consumes more memory and decreases CPU cache hits. Compressed Oops (-XX:+UseCompressedOops) "compresses" 8-byte pointers back to 4 bytes.
- Logic: Since objects are 8-byte aligned, the JVM can store "offsets" instead of "byte addresses," allowing 4 bytes to address up to $4GB \times 8 = 32GB$ of heap.
- The Limit: If you set your heap size to 32GB or more, the JVM disables compression and reverts to 8-byte "fat pointers," often resulting in less usable space than a 31GB heap.
7. The Lifecycle of an Object (new Object())
- Class Loading Check: JVM checks if the class's "Blueprint" is already in the Runtime Constant Pool. If not, the class-loading process begins.
- Memory Allocation: JVM carves out space in the Heap.
- Bump-the-pointer: Used with compacting collectors (sequential free space).
- Free List: Used when memory is fragmented (CMS).
- TLAB (Thread Local Allocation Buffer): Each thread pre-allocates a private slice in Eden to avoid locking overhead during allocation.
- Zero Initialization: Memory is wiped clean; fields are set to default values (0, null, false).
- Header Setting: The ID (Mark Word) and metadata link (Klass Pointer) are stamped onto the object.
<init>Execution: The developer's constructor logic runs, initializing fields to business-logic values.
Summary Matrix
| Region | Scope | Content | Lifetime |
|---|---|---|---|
| PC Register | Thread | Instruction address | Thread lifespan |
| VM Stack | Thread | Local variables, calc workspace | Thread lifespan |
| Heap | App | Objects & Arrays | Controlled by GC |
| Metaspace | App | Class metadata, constants | App lifecycle |
| Direct Memory | OS | I/O buffers (Zero-copy) | Manual/Cleaner |