The JVM splits memory into areas with different lifetimes: objects on the heap, method calls on per-thread stacks, class metadata in metaspace.
The runtime data areas
| Area | Shared? | Holds | Error when full |
|---|---|---|---|
| Heap — young gen (Eden + 2 Survivor spaces) | All threads | Newly created objects and arrays | OutOfMemoryError: Java heap space |
| Heap — old (tenured) gen | All threads | Objects that survived several young GCs | OutOfMemoryError: Java heap space |
| JVM stack (one per thread) | Per thread | One frame per method call: local variables, operand stack, return address | StackOverflowError |
| Metaspace (native memory, Java 8+) | All threads | Class metadata: method bytecode, field layout, runtime constant pool | OutOfMemoryError: Metaspace |
| PC register | Per thread | Address of the bytecode instruction being executed | — |
| Native method stack | Per thread | Frames of native (C/C++) methods called through JNI | StackOverflowError |
| Code cache | All threads | Machine code produced by the JIT compiler | JIT stops compiling (warning) |
What lives where
public class Main {
static class Box { int value; }
public static void main(String[] args) {
int a = 10; // primitive local → in main's stack frame
int b = a; // copies the value
b++;
Box x = new Box(); // reference x on the stack, Box object on the heap
Box y = x; // copies the reference, not the object
y.value = 42;
System.out.println(a + " " + b);
System.out.println(x.value + " " + (x == y));
}
}10 11
42 trueStack frames and StackOverflowError
Call
Calling a method pushes a new frame onto the current thread's stack.
Execute
The frame holds the method's parameters and locals, plus an operand stack the bytecode works on.
Return
The frame is popped — its locals disappear instantly. No GC is needed for stack memory.
public class Main {
static int depth = 0;
static void recurse() {
depth++;
recurse(); // no base case → a new frame on every call
}
public static void main(String[] args) {
try {
recurse();
} catch (StackOverflowError e) {
System.out.println("StackOverflowError after more than 1000 frames: " + (depth > 1000));
}
}
}StackOverflowError after more than 1000 frames: trueOutOfMemoryError
Run with a deliberately tiny heap (-Xmx64m) and ask for a 400 MB array:
public class Main {
public static void main(String[] args) {
try {
int[] huge = new int[100_000_000]; // ~400 MB
System.out.println(huge.length);
} catch (OutOfMemoryError e) {
System.out.println("OutOfMemoryError: " + e.getMessage());
}
}
}OutOfMemoryError: Java heap spaceStackOverflowError | OutOfMemoryError | |
|---|---|---|
| Area | A thread's stack | Heap, metaspace, direct buffers, native threads… |
| Typical cause | Unbounded / too deep recursion | Leak (objects stay reachable), or heap simply too small |
| Fix | Base case, iteration, or bigger -Xss | Fix the leak (heap dump), then size -Xmx |
| Type | Both extend Error (via VirtualMachineError) — not meant to be caught in normal code |
Sizing flags
| Flag | Meaning | Example |
|---|---|---|
-Xms | Initial heap size | -Xms512m |
-Xmx | Maximum heap size (default: 1/4 of physical RAM) | -Xmx2g |
-Xss | Stack size per thread | -Xss1m |
-XX:MaxMetaspaceSize | Cap metaspace (unbounded by default) | -XX:MaxMetaspaceSize=256m |
-XX:+HeapDumpOnOutOfMemoryError | Write a .hprof heap dump when OOM happens | with -XX:HeapDumpPath=/tmp |
$ java -Xms256m -Xmx1g -Xss512k -XX:+HeapDumpOnOutOfMemoryError App.java
$ java -XX:+PrintFlagsFinal -version | grep -E "MaxHeapSize|ThreadStackSize"Escape analysis
The JIT checks whether an object escapes the method that creates it (returned, stored in a field, passed to another thread). If it doesn't, the JIT may scalar-replace it — keep its fields in registers/stack slots and never allocate it on the heap at all.
public class Main {
record Point(int x, int y) {}
static int distanceSquared(int x, int y) {
Point p = new Point(x, y); // never escapes → candidate for scalar replacement
return p.x() * p.x() + p.y() * p.y();
}
public static void main(String[] args) {
long sum = 0;
for (int i = 0; i < 1_000; i++) sum += distanceSquared(i, i);
System.out.println(sum);
}
}665667000Where are local variables, objects and static variables stored?
What replaced PermGen and why?
Is the stack or the heap thread-safe?