Learning goals
By the end of this chapter you should be able to:
- Name JVM memory regions: heap, stack, metaspace, code cache
- Answer “where does this variable live?” for locals, fields, statics, and arrays
- Connect object references to the heap layout you use when choosing collections
- Relate memory regions to
OutOfMemoryErrorandStackOverflowError
From your code to JVM layout
You already write new ArrayList<>(), store User references in Map, and pass primitives to methods. Each of those choices implies objects on the heap and references on the stack.
void register(User user) { // 'user' reference → stack frame
List<String> roles = new ArrayList<>(); // ref stack, list object heap
roles.add("ADMIN");
}
This chapter maps that mental model to the official JVM regions: what is shared vs per-thread, what the GC manages, and what fails when a region fills up.
The detailed breakdown below builds on notes you have already written about objects and references — read it as the full runtime picture behind every collection and new you use in application code.
Overview
When the JVM starts, it divides memory into well-defined regions: heap, stack, metaspace, code cache, and a few smaller ones. Each region holds different things and is managed differently — knowing which is which is the foundation for understanding garbage collection, OutOfMemoryError, and StackOverflowError.
Table of contents
- The big picture
- Heap — where objects live
- Stack — per-thread method frames
- Metaspace — class metadata
- Code cache — JIT-compiled code
- PC register & native method stack
- Heap vs Stack — where does this variable live?
- Common memory errors
- Interview questions
- Summary
1. The big picture
+-------------------- JVM Process --------------------+
| |
| +---------------- Shared across threads --------+ |
| | Heap | |
| | ├─ Young Generation (Eden + Survivors) | |
| | └─ Old (Tenured) Generation | |
| | Metaspace (class metadata) | |
| | Code Cache (JIT compiled methods) | |
| +-----------------------------------------------+ |
| |
| +---------------- Per thread ------------------+ |
| | Stack (method frames) | |
| | PC Register (current instruction) | |
| | Native Method Stack (for JNI calls) | |
| +----------------------------------------------+ |
+-----------------------------------------------------+
Two camps:
- Shared regions live for the lifetime of the JVM (or longer for some). Multiple threads access them.
- Per-thread regions are created when a thread starts and destroyed when it ends.
2. Heap — where objects live
The heap is the big shared area where every object created with new lives. It’s also the area the garbage collector manages.
String s = new String("hello"); // the String object is on the heap
List<Integer> list = new ArrayList<>(); // the list and its array → heap
The heap is split into generations for efficient GC:
- Young generation — Eden + two Survivor spaces. New objects start here. Most die young.
- Old (Tenured) generation — objects that survived several GCs get promoted here.
Sized with:
-Xms512m # initial heap size
-Xmx2g # maximum heap size
-XX:MaxRAMPercentage=75 # use 75% of container memory (better in Docker/K8s)
When the heap is full and the GC can’t free space → OutOfMemoryError: Java heap space.
Deep dive on how the GC manages this area: 02-garbage-collection.md.
3. Stack — per-thread method frames
Every thread has its own stack. The stack holds one frame per active method call.
A frame contains:
- The method’s local variables.
- The operand stack (for intermediate values during bytecode execution).
- A reference back to the method’s class.
void outer() {
int x = 10; // x is on outer's frame
inner(x); // pushes a new frame for inner
}
void inner(int n) {
int doubled = n * 2; // doubled is on inner's frame
} // inner's frame popped here
Key properties:
- Fast — push/pop on a frame, no GC needed.
- Small — usually 256 KB to 1 MB per thread. Set with
-Xss512k. - Frames are automatically freed when the method returns.
- Deep or infinite recursion →
StackOverflowError.
Important: the stack holds primitives (int, long, etc.) and object references. The objects themselves still live on the heap.
void example() {
int n = 5; // n itself is on the stack
String s = new String("hi"); // s is a reference on the stack; "hi" object is on the heap
}
4. Metaspace — class metadata
Metaspace holds info about classes: method bytecode, field layouts, static fields, runtime constant pool.
- Introduced in Java 8, replacing the older fixed-size PermGen.
- Lives in native memory (outside the heap).
- Grows automatically as you load classes. Cap it with
-XX:MaxMetaspaceSize=256m. - Filling it up →
OutOfMemoryError: Metaspace— usually caused by classloader leaks (loading the same classes over and over without unloading).
5. Code cache — JIT-compiled code
The JVM starts by interpreting bytecode. After a method runs many times, the JIT compiler (HotSpot has C1 and C2) compiles it to native machine code for speed. That compiled code lives in the code cache.
- Size:
-XX:ReservedCodeCacheSize=240m. - If full, the JIT stops compiling new methods → app slows down (rare, but possible in very long-running apps).
6. PC register & native method stack
Two smaller per-thread regions that you mention by name in interviews:
- PC register — holds the address of the current JVM instruction being executed by that thread.
- Native method stack — used when Java calls into native code (JNI), e.g. native libraries.
7. Heap vs Stack — where does this variable live?
| Variable | Lives on |
|---|---|
int n = 5 (local in a method) |
Stack |
String s = new String(...) (local) |
Reference on stack, object on heap |
| Method parameter | Stack (same rules) |
| Instance field of an object | Heap (inside the owning object) |
static field |
Heap (was Metaspace before Java 8) |
Array (new int[100]) |
Heap |
| Class metadata, bytecode | Metaspace |
Rule: the variable name lives on the stack; the object it points to lives on the heap.
8. Common memory errors
| Error | Cause | Fix |
|---|---|---|
OutOfMemoryError: Java heap space |
Heap full, GC can’t free anything | Larger -Xmx, or fix memory leak |
OutOfMemoryError: Metaspace |
Too many classes loaded | Cap MaxMetaspaceSize, find classloader leak |
OutOfMemoryError: GC overhead limit exceeded |
GC running constantly, freeing <2% per run | Heap too small or leak |
OutOfMemoryError: unable to create new native thread |
OS thread limit hit | Reduce threads, use virtual threads (Java 21+) |
StackOverflowError |
Recursion too deep | Convert to iteration, or grow -Xss |
9. Interview questions
Q: What’s the difference between heap and stack?
A: Stack is per-thread, holds local variables and method frames, and is freed automatically when methods return. Heap is shared, holds all objects created with new, and is managed by the garbage collector.
Q: Where do primitives and references live? A: Local primitives and references live on the stack (inside the method’s frame). The objects those references point to live on the heap.
Q: What is Metaspace? What did it replace? A: Metaspace holds class metadata (bytecode, field layouts, runtime constant pool). It replaced PermGen in Java 8. Unlike PermGen, it grows automatically and lives in native memory, not the heap.
Q: Where do static fields live? A: On the heap (since Java 8). Before Java 8 they were in PermGen.
Q: What causes StackOverflowError?
A: A thread’s stack ran out of space — almost always deep or infinite recursion. Grow with -Xss or switch to iteration.
Q: What’s the difference between OutOfMemoryError: Java heap space and Metaspace?
A: Heap space — too many objects, often a leak in your code (long-lived collections, listeners). Metaspace — too many classes, usually a classloader leak in apps that redeploy without restarting (old app servers).
Q: What is the code cache?
A: Where the JIT compiler stores native code compiled from hot methods. Sized with -XX:ReservedCodeCacheSize.
Q: Are arrays stored on the heap or stack? A: Arrays are objects, so they’re always on the heap. The reference to them lives on the stack if declared as a local variable.
Q: How is memory allocated to threads? A: Each thread gets its own stack (~512 KB default), PC register, and native method stack. All threads share the heap, metaspace, and code cache.
10. Summary
| Region | Shared / per-thread | Holds | Failure |
|---|---|---|---|
| Heap | Shared | All objects, arrays, static fields | OOM: Java heap space |
| Stack | Per-thread | Local variables, method frames | StackOverflowError |
| Metaspace | Shared (native) | Class metadata | OOM: Metaspace |
| Code Cache | Shared (native) | JIT-compiled code | JIT stops compiling |
| PC Register | Per-thread | Current instruction pointer | — |
| Native Stack | Per-thread | JNI call frames | OS-level limit |
Mental model: the stack holds the name; the heap holds the thing.
Next steps
- Garbage collection — how the JVM cleans up the heap
- Data structures — what lives in the heap
- Multithreading — why each thread needs its own stack
Happy Learning
Practice checkpoint
-
Local
int n = 5inside a method — stack or heap? Answer:nlives in the method’s stack frame. -
Instance field
String nameon aUserobject — where? Answer: inside theUserobject on the heap. -
static Map<String, Config> CACHE— where after Java 8? Answer: static field storage on the heap (Class object / related metadata in metaspace). -
Deep recursion with no base case — which error first? Answer:
StackOverflowError(thread stack exhausted).
Interview angles
- Heap vs stack: shared GC-managed objects vs per-thread frames freed on return.
- Metaspace vs PermGen: class metadata in native memory; grows automatically since Java 8.
- Reference vs object: stack holds reference value;
newallocates on heap. - Tuning entry points:
-Xmx,-Xss,-XX:MaxMetaspaceSize— know symptom → flag direction.