⚙️ JVM Internals & Memory · Advanced

Runtime memory areas in Java

Heap, stacks, metaspace, PC register, native method stack.

🧩 The mysteryTwo crashes, same app: StackOverflowError on Monday, OutOfMemoryError: Metaspace on Friday. Both are "out of memory". So why do they need completely different fixes?

The shared rooms

Some memory belongs to the whole JVM and all threads share it: the heap (every object and array), metaspace (class metadata, stored in native memory outside the heap) and the code cache (machine code produced by the JIT).

Each thread's private desk

Every thread also gets its own private areas: a JVM stack with one frame per method call (holding locals and partial results), a PC register (the address of the instruction it's running) and a native method stack for native code.

🔮 Predict it

Dive until it breaks

What does this print?

int calls = 0;
void down() { calls++; down(); }
void main() {
    try {
        down();
    } catch (StackOverflowError e) {
        boolean deep = calls > 100;
        System.out.println("caught: " + deep);
    }
}
  1. caught: true
  2. Throws OutOfMemoryError
  3. caught: false
Show the answer

Each call to down() pushes a new frame onto this thread's stack until it's full, which throws StackOverflowError. By then far more than 100 frames were pushed. Deep recursion fills the stack, not the heap.

Goodbye PermGen

Up to Java 7, class metadata lived in PermGen, a fixed-size part of the heap tuned with -XX:MaxPermSize. Java 8 removed PermGen and replaced it with metaspace in native memory. It grows as needed; cap it with -XX:MaxMetaspaceSize.

An old runbook

✗ Java 7 era
-XX:MaxPermSize=256m

PermGen no longer exists. Recent JDKs don't even start with this flag (Unrecognized VM option).

✓ Modern Java
-XX:MaxMetaspaceSize=256m

Caps metaspace, where class metadata lives since Java 8.

🤔 Think first

Free thread safety

Why can two threads run the same method at once without their local variables getting mixed up?

Think about it, then reveal the answer

Locals live in each thread's own stack frame, which no other thread can see. Only objects on the shared heap can be reached by several threads.

💼 In the real world

Pick the right knob

On call, the error names the area, and the area names the fix: StackOverflowError means one thread's stack (-Xss, or less recursion). OutOfMemoryError: Java heap space points at objects (-Xmx, or a leak). OutOfMemoryError: Metaspace points at classes (-XX:MaxMetaspaceSize, or a class loader leak).

Key takeaways

  1. Shared: heap, metaspace, code cache
  2. Per thread: JVM stack, PC register, native method stack
  3. Deep recursion fills the stack: StackOverflowError
  4. Metaspace is native memory, outside the Java heap
🤯 Did you know?

The JVM spec says that while a thread runs a native method, the value of its PC register is undefined. It only tracks bytecode positions.

Practice questions

What does this print?

int depth = 0;
void dive() { depth++; dive(); }
void main() {
    try {
        dive();
    } catch (StackOverflowError e) {
        System.out.println("stack full");
    }
    System.out.println(depth > 1000);
}
  1. stack full true
  2. Throws OutOfMemoryError
  3. stack full false
  4. true
Check your answer

stack full true. Every call to dive() pushes a frame on the thread's stack until it overflows. The StackOverflowError is caught, and by then thousands of frames had been pushed.

An old Java 7 runbook says to tune -XX:MaxPermSize. In modern Java, where does class metadata live?

  1. In metaspace, in native memory outside the heap
  2. In each thread's stack
  3. In PermGen, which is now unlimited
  4. In the young generation
Check your answer

In metaspace, in native memory outside the heap. PermGen was removed in Java 8 and replaced by metaspace. It grows in native memory and can be capped with -XX:MaxMetaspaceSize.

Next: a method receives an array and replaces it with a new one, but the caller never notices. Stack vs heap, up close.