JVM memory: stack vs heap
- Last updated
- Prerequisites:
- Java method and object basics
- Familiarity with references and primitives
- java
- jvm
- memory
- stack
- heap
- garbage-collection
Read at your depth
The practical view
Each thread has its own stack holding method frames: local variables, primitive values, and object references live here; the objects themselves live on the heap. When a method returns, its frame is popped and its locals are gone. The heap is shared by all threads and holds all objects and arrays; the garbage collector reclaims objects no longer reachable from GC roots (static fields, stack frames, JNI references). new always allocates on the heap (though escape analysis can place small objects on the stack). StackOverflowError comes from unbounded recursion (frames exceed stack size); OutOfMemoryError: Java heap space from exhausting the heap.
Legacy vs modern
Allocation that escapes vs allocation eliminated by escape analysis
Objects that escape to the heap drive GC pressure; the JIT can stack-allocate or scalar-replace non-escaping objects, eliminating the allocation entirely.
long sum = 0;
for (int i = 0; i < N; i++) {
Point p = new Point(i, i); // escapes? if returned/stored, heap // ❌ OLD: new Point() per iteration → Eden alloc + GC pressure
sum += p.distance();
}// Same loop after escape analysis: fields promoted to locals.
double total = 0;
for (int i = 0; i < N; i++) {
int px = i, py = i; // scalar replacement // ✅ NEW: scalar replacement → fields in registers, no object
total += Math.hypot(px, py); // no object exists at all
}Interview gotchas
Context
The interviewer wants to separate memorized claims ('stack is fast') from the actual mechanisms (pointer bump, frame pop, escape analysis).
The mechanical answer
Stack allocation is a pointer bump in the current frame and deallocation is a frame pop — no locking, no bookkeeping, no GC interaction, and excellent cache locality because the frame is hot. The JVM does not allocate Java objects on the stack in the classic sense, but escape analysis in C2 lets it eliminate allocations entirely: if an object never escapes the method (not stored in a field, not returned, not passed to a call the JIT can see), its fields are scalar-replaced into registers and stack slots. So the 'stack allocation' win is really allocation elimination plus the frame's natural locality.
Trap
The trap answer is 'the JVM puts objects on the stack when they are small'. Escape analysis does not look at size — it looks at the object's lifetime relative to the method (escape state: NoEscape/ArgEscape/GlobalEscape). Also, some candidates claim all allocations go through malloc — HotSpot's TLAB bump allocation bypasses the allocator entirely. Mentioning TLABs and scalar replacement is what makes the answer senior-level.
Context
This checks whether the candidate understands reachability — the definition of liveness — rather than just describing 'the GC deletes unused objects'.
The mechanical answer
GC roots are the starting points for reachability tracing: live thread stacks (each frame's local variables and operand stack slots that hold references), static fields, JNI global/局部 references, and active monitor locks. The collector walks from these roots across the object graph; anything unreachable is garbage. The stack is therefore load-bearing for GC — a local variable that still holds a reference keeps an object alive even if logically dead. This is why setting large references to null in long loops matters, and why the JVM must enumerate roots precisely (exact GC) rather than conservatively scanning registers.
Trap
The naive answer is 'GC roots are the main method'. Roots are the union of all thread stacks, statics, and JNI handles — the main method's frame is just one stack frame among many. Another trap: 'nulling a local is always useless because the JIT optimizes it' — the JIT can also reorder/eliminate stores, so the reliable pattern is scope-based or explicit null only where needed; the precise model (OopMap at safepoints) is what the interviewer wants to hear about.