Memory Model & Runtime Data Areas
Runtime Data Areas Overview
The JVM divides the memory it manages into several runtime data areas when executing a Java program. Some are created and destroyed with threads, while others exist for the lifetime of the JVM:
┌──────────────────────────────────────────┐
│ Thread-Shared (GC Managed) │
│ ┌─────────────────┐ ┌────────────────┐ │
│ │ Heap │ │ Method Area / │ │
│ │ │ │ Metaspace │ │
│ └─────────────────┘ └────────────────┘ │
├──────────────────────────────────────────┤
│ Thread-Private (No GC) │
│ ┌──────┐ ┌─────────┐ ┌───────────────┐ │
│ │ PC │ │ VM Stack │ │ Native Stack │ │
│ └──────┘ └─────────┘ └───────────────┘ │
├──────────────────────────────────────────┤
│ Direct Memory │
└──────────────────────────────────────────┘
PC Register
The PC Register is a small memory space that acts as a line number indicator for the bytecode being executed by the current thread.
- Thread-private: Each thread has its own PC Register
- The only area that never throws OOM: The JVM specification defines no OutOfMemoryError conditions for it
- When executing native methods: The PC value is undefined
VM Stack
The VM Stack describes the memory model for Java method execution. Each method invocation creates a Stack Frame that stores the local variable table, operand stack, dynamic linking, and method return address.
Stack Frame Structure
┌───────────────────────┐
│ Local Variable Table │ ← Method parameters + local variables
├───────────────────────┤
│ Operand Stack │ ← Intermediate computation results
├───────────────────────┤
│ Dynamic Linking │ ← Reference to method in runtime constant pool
├───────────────────────┤
│ Method Return Address│ ← Return position after normal/exceptional exit
├───────────────────────┤
│ Additional Info │ ← Debug info, etc.
└───────────────────────┘
Local Variable Table
- Based on Slots as the fundamental unit; 64-bit types (long/double) occupy 2 Slots
- Slot 0 in instance methods holds the
thisreference - The size of the local variable table is determined at compile time and does not change at runtime
- GC Root: References in the local variable table can serve as GC Roots
Exceptions
- StackOverflowError: The thread’s requested stack depth exceeds the allowed limit (common with recursive calls)
- OutOfMemoryError: Unable to allocate a new stack (use
-Xssto set stack size, typically 256K–1M)
Native Method Stack
Serves native methods used by the JVM. HotSpot combines the VM Stack and Native Method Stack into one. It can also throw StackOverflowError and OutOfMemoryError.
Heap
The Heap is the largest piece of memory managed by the JVM, shared by all threads, and created at JVM startup. Nearly all object instances and arrays are allocated on the heap.
Generational Structure
HotSpot employs a generational collection strategy, dividing heap memory into:
┌──────────────────────────────────────────────────┐
│ Heap │
├───────────────────────┬──────────────────────────┤
│ Young Generation │ Old Generation │
├──────────┬────────────┤ │
│ Eden │ Survivor │ │
│ │ S0 │ S1 │ │
└──────────┴──────┴─────┴──────────────────────────┘
- Eden: New objects are first allocated in Eden
- Survivor (S0/S1 or From/To): Objects surviving GC are moved from Eden to Survivor
- Old Generation: Objects surviving multiple GC cycles are promoted to the old generation
Default ratios: Young:Old = 1:2 (-XX:NewRatio=2), Eden:S0:S1 = 8:1:1 (-XX:SurvivorRatio=8)
Heap Memory Parameters
| Parameter | Description | Recommended Value |
|---|---|---|
-Xms |
Initial heap size | Same as -Xmx to avoid dynamic expansion |
-Xmx |
Maximum heap size | 50–80% of physical memory |
-Xmn |
Young generation size | 30–40% of heap |
-XX:NewRatio |
Old/Young generation ratio | 2 (default) |
-XX:SurvivorRatio |
Eden/Survivor ratio | 8 (default) |
-XX:MaxTenuringThreshold |
Age threshold for promotion to old gen | 15 (default) |
OOM Scenario
// Heap OOM example
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // Allocate 1MB each time
}
// java.lang.OutOfMemoryError: Java heap space
Method Area and Metaspace
From PermGen to Metaspace
| Version | Implementation | Storage Location | Size Limit |
|---|---|---|---|
| Java 7 and earlier | Permanent Generation (PermGen) | Heap memory | -XX:MaxPermSize (default 64M/82M) |
| Java 8+ | Metaspace | Native memory | -XX:MaxMetaspaceSize (default unlimited) |
Metaspace uses native memory and is no longer constrained by heap size, resolving the OOM-prone nature of PermGen.
Method Area Contents
- Class metadata: Class name, access modifiers, field descriptors, method descriptors
- Runtime constant pool: Compile-time generated literals and symbolic references
- Static variables: Class-level static variables (moved to the heap since Java 7+)
- JIT-compiled code cache
Runtime Constant Pool
The constant pool in the class file is stored in the runtime constant pool of the method area after class loading. Compared to the class file constant pool, the runtime constant pool is more dynamic — new constants can be added at runtime, such as via the String.intern() method.
// String.intern() example
String s1 = new String("hello"); // Creates object on the heap
String s2 = s1.intern(); // Attempts to add "hello" to the string constant pool
String s3 = "hello"; // Directly references the constant pool
System.out.println(s2 == s3); // true
Metaspace OOM
// Dynamically generating classes leading to Metaspace OOM
// Common with CGLIB, Spring AOP generating large numbers of proxy classes
// java.lang.OutOfMemoryError: Metaspace
Parameter: -XX:MaxMetaspaceSize=256m to limit the maximum Metaspace size
Direct Memory
Direct Memory is not part of the runtime data areas defined by the JVM specification, but it is frequently used:
- NIO:
ByteBuffer.allocateDirect()allocates off-heap direct memory, avoiding data copying between the heap and kernel during I/O - JNI: Memory directly allocated by native code
- Thread stacks: Memory occupied by each thread stack
Direct memory is not constrained by heap size but is limited by physical memory. -XX:MaxDirectMemorySize can set an upper limit (default equals -Xmx).
// Direct memory OOM
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 1024);
// java.lang.OutOfMemoryError: Direct buffer memory
OOM Quick Reference Table
| Area | OOM Message | Trigger Condition | Troubleshooting Parameter |
|---|---|---|---|
| Heap | Java heap space |
Too many objects | -XX:+HeapDumpOnOutOfMemoryError |
| Stack | StackOverflowError |
Excessive recursion depth | -Xss |
| Metaspace | Metaspace |
Excessive class loading | -XX:MaxMetaspaceSize |
| Direct Memory | Direct buffer memory |
Excessive NIO usage | -XX:MaxDirectMemorySize |
| Native Memory | Cannot reserve enough space |
Insufficient process memory | Reduce heap/Xss |
Summary
This chapter covered the JVM runtime data areas in detail, including the characteristics of each region. Understanding the heap’s generational structure, the evolution from PermGen to Metaspace, and OOM trigger conditions for each area provides the foundation for learning about garbage collection and performance tuning. The next chapter will dive into object creation and memory layout in the JVM.
Comments