Mastering JVM Memory: From Heap to Metaspace and Beyond
This article provides a comprehensive overview of the Java Virtual Machine memory architecture, covering heap layout, object allocation, Metaspace, stack frames, native method stacks, program counters, direct memory, and code cache, complete with configuration tips and practical code examples.
Preface
This JVM series is a summary of knowledge points collected during the author's learning process, aiming to help readers quickly grasp key JVM concepts. For a more systematic and detailed study, readers should consult professional books and documentation.
Article outline:
JVM memory area overview
Heap space allocation and overflow demonstration
How memory is allocated when creating a new object
Method area to Metaspace
What is a stack frame? Its contents and understanding
Native method stack
Program Counter
Code Cache
Note: Distinguish between JVM memory structure (layout) and JMM (Java Memory Model)!
Overview
Memory is a crucial system resource that acts as a bridge between disk and CPU, supporting the real‑time execution of OS and applications. The JVM memory layout defines how Java requests, allocates, and manages memory, ensuring efficient and stable operation.
The above diagram shows a classic Java memory layout (the heap area is drawn smaller than it actually is).
If we classify by whether threads share the area, the following diagram applies:
Understanding whether an area is thread‑shared becomes natural once you know the purpose of each region; no need for rote memorization.
Now let's explore each region.
Heap
1. Introduction
The heap is the primary region where OutOfMemoryError occurs. It is the largest memory area, shared by all threads, storing almost all object instances and arrays. With JIT compilation and escape analysis, stack allocation and scalar replacement can reduce heap allocations.
Related topic: Escape analysis in JIT compilation – see “Deep Understanding of Java Escape Analysis”.
The Java heap is managed by the garbage collector and is divided into generations: young and old. The young generation includes Eden, From Survivor, and To Survivor spaces. Thread‑local allocation buffers (TLAB) may also exist.
2. Adjustment
The heap can be physically non‑contiguous as long as it is logically contiguous. It can be fixed‑size or dynamically resized at runtime.
How to adjust?
Set parameters such as -Xms256M -Xmx1024M to define initial and maximum heap size. In production, Xms and Xmx are often set equal to avoid heap resizing overhead after GC.
3. Default allocation
Typical default ratios: InitialSurvivorRatio=8, NewRatio=2. With a 40 MB Eden, each Survivor is 5 MB, young generation totals 50 MB, old generation 100 MB, making the heap 150 MB.
<code>java -XX:+PrintFlagsFinal -version</code>Relevant flags:
-XX:InitialSurvivorRatio – initial Eden/Survivor ratio
-XX:NewRatio – Old/Young memory ratio
4. Heap overflow demo
<code>/*** VM Args:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError */
public class HeapOOMTest {
public static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
List<byte[]> byteList = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
byte[] bytes = new byte[2 * _1MB];
byteList.add(bytes);
}
}
}
</code> <code>java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid32372.hprof ...
</code>The flag -XX:+HeapDumpOnOutOfMemoryError causes the JVM to output heap information when an OOM occurs.
Object allocation process
Most objects are created in Eden; when Eden fills, a Young GC (YGC) occurs. Live objects are moved to Survivor spaces, which swap roles each GC. Objects that exceed Survivor capacity are promoted to the old generation. The -XX:MaxTenuringThreshold controls promotion after a number of YGCs (default 15).
For more on GC terminology, see the GC chapter of this series.
Metaspace
In HotSpot, the former Permanent Generation (method area) stored class metadata and the constant pool. PermGen caused frequent OOM errors, so Java 8 replaced it with Metaspace, which resides in native memory and is limited only by the host’s memory.
Key JVM options:
-XX:MetaspaceSize – initial size
-XX:MaxMetaspaceSize – maximum size (no default limit)
-XX:MinMetaspaceFreeRatio
-XX:MaxMetaspaceFreeRatio
Java Virtual Machine Stack
Each thread has its own JVM stack, which stores stack frames. A stack frame contains the local variable table, operand stack, dynamic linking information, and the return address.
1. Local variable table
Allocated at compile time; its size does not change at runtime.
<code>public int test(int a, int b) {
Object obj = new Object();
return a + b;
}
</code>2. Operand stack
Used by the stack‑based execution engine. Example:
<code>public class OperandStackTest {
public int sum(int a, int b) {
return a + b;
}
}
</code>Disassembled bytecode shows a stack depth of 2 and locals of 3.
3. Dynamic linking
Each frame holds a reference to the method’s constant‑pool entry for dynamic linking.
4. Return address
Methods may exit normally (RETURN, IRETURN, etc.) or via exception; the return address is restored accordingly.
Native Method Stack
Serves native methods similarly to the JVM stack; some JVMs combine the two.
Program Counter
A small thread‑private memory that records the current bytecode line number, enabling the CPU to resume execution after thread switches.
Direct Memory
Not part of the JVM runtime data area, but used via NIO’s DirectByteBuffer . It resides outside the Java heap and is limited by the host’s physical memory.
Code Cache
The region where the JVM stores compiled native code (nmethods). The JIT compiler is the main consumer of this cache.
References
《深入理解Java虚拟机》 – 周志明
《码出高效》
Metaspace in Java 8
JVM instruction set illustration
Introduction to JVM Code Cache
Sanyou's Java Diary
Passionate about technology, though not great at solving problems; eager to share, never tire of learning!
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.