Heap vs Stack Memory in the JVM — Lifetime, Errors, and What Lives Where
Fast recall
Quick revision
Core answer
-Xss), and exhaustion throws `StackOverflowError` (typically from deep/infinite recursion). Heap is shared by all threads, stores all objects and arrays (everything created with new), is managed by the garbage collector, has configurable size (-Xms initial, -Xmx max), and exhaustion throws `OutOfMemoryError: Java heap space` (typically from object leaks or undersized heap). What lives where: Stack holds primitives as values (int x = 5), object references as pointers (the variable holds an address; the object itself is on the heap), method-call frames (one per active call). Heap holds all objects including String literals (in the String pool, which has been on the heap since Java 7, no longer in PermGen), arrays, lambdas, and closures. Special area: Metaspace (Java 8+) holds class metadata (replaced PermGen) and uses native memory — not heap. Lifetime: stack data lives until the method returns; heap objects live until no live references remain (GC reclaims them). Escape analysis (a JIT optimization) can sometimes allocate short-lived objects on the stack to skip GC, but this is invisible to the programmer. For interviews: pair the concept with the error — 'StackOverflowError = stack exhaustion = recursion bug; OutOfMemoryError = heap exhaustion = leak or undersized heap'.Whether the candidate knows what lives where (primitives vs references vs objects), the per-thread vs shared distinction, and which error maps to which area
Saying String literals live on the stack (they're in the String pool on the heap since Java 7). Confusing 'reference is on the stack' with 'object is on the stack'. Forgetting Metaspace is in native memory, not heap
Discuss escape analysis (JIT can stack-allocate short-lived objects), -Xss tuning for thread-heavy apps, the String pool's location since Java 7, and how virtual threads (Java 21) change stack memory characteristics
Interview-ready guide
Interview answer
Stack vs Heap — the fundamental split
Stack
- Per-thread (private)
- Method frames
- Locals, parameters, references, primitives
- LIFO: push on call, pop on return
- Fixed size per thread (512KB–1MB default)
-Xssto tune- StackOverflowError on exhaustion
Heap
- Shared by all threads
- All objects and arrays
- GC-managed
- Configurable size:
-Xmsinitial,-Xmxmax - OutOfMemoryError on exhaustion
What lives where
Stack
- Primitives by value:
int x = 5 - Object references (pointers to heap)
- Method frame data
Heap
- Objects and arrays (everything
newd) - String pool (since Java 7; was PermGen)
- Lambdas / closures (they're objects)
Special area: Metaspace (Java 8+)
- Class metadata
- Native memory, NOT heap
- Replaced PermGen
- Can OOM separately
Critical distinction
- Reference is on stack
- Object itself is on heap
- Variable holds an address (pointer); object lives elsewhere
Lifetime
Stack
- Frame lives until method returns
- Auto-cleanup; no GC needed
- Fast (just pointer adjustment)
Heap
- Object lives until no live references
- GC reclaims unreferenced objects
- Slower (allocation + GC overhead)
StackOverflowError
Cause
- Stack exhausted
- Usually deep / infinite recursion
- Sometimes huge frames
Diagnosis
- Stack trace shows long repeating frames
- IDE debugger or jstack reveals chain
Fix
- Convert recursion to iteration
- Add base case
- Increase
-Xss(treats symptom)
OutOfMemoryError: Java heap space
Cause
- Heap exhausted
- Leak (cache without eviction, ThreadLocal not cleared, listener not removed)
- Undersized heap
- Loading too much at once
Diagnosis
-XX:+HeapDumpOnOutOfMemoryErrorfor heap dump- Analyze with Eclipse MAT (dominator tree, leak suspects)
- jmap, jstat for live metrics
Fix
- Find and fix leak
- Raise
-Xmxif legitimate growth - Stream / paginate
- SoftReference / WeakReference for caches
Other OOMs
GC overhead limit exceeded
- Spending >98% time in GC, recovering <2%
- Usually leak
Metaspace
- Class metadata exhausted
- Classloader leak (common in app servers)
- Cap with
-XX:MaxMetaspaceSize
Direct buffer memory
- NIO direct buffers in native memory
- Cap with
-XX:MaxDirectMemorySize
Unable to create native thread
- OS thread limit, not JVM
- Reduce
-Xssor thread count
Tuning notes
Stack size (-Xss)
- Default 512KB–1MB
- Server with thousands of threads: lower
-Xss(e.g., 256k) - Deep recursion: raise
- 1MB × 5000 threads = 5GB just stacks
Heap size
-Xms == -Xmxfor production (no resize)-XX:MaxRAMPercentage=75for containers
Modern
- Always
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/heap.hprof
Escape analysis (JIT optimization)
- C2 can prove an object doesn't escape the method
- Allocate on stack instead of heap
- Zero GC overhead for that object
- Invisible to programmer
- Mention to stand out in interviews
Java 21 virtual threads (Project Loom)
- Stack stored on heap (continuation stack)
- Resizable; no per-thread stack overhead
- Millions of threads possible
- Changes the stack memory landscape entirely
Common bugs
- 1Saying String literals are on the stack (they're in the String pool on the heap, since Java 7)
- 2'Object is on the stack' (reference is; object is on heap, unless escape analysis)
- 3Forgetting Metaspace is native memory (not part of -Xmx)
- 4Confusing OOM types
- 5Not enabling heap dump on OOM
For interviews
- Pair each error with its area
- Stack → SOE → recursion
- Heap → OOM → leak
- Mention escape analysis, virtual threads
Recommendation: The stack is per-thread, holds method frames with locals, parameters, references, and primitives, has a fixed size per thread (512KB–1MB default, configured with -Xss), and exhaustion throws `StackOverflowError` — typically caused by deep or infinite recursion. The heap is shared across all threads, holds all objects and arrays (everything created with new), is GC-managed, has configurable size (-Xms initial, -Xmx max), and exhaustion throws `OutOfMemoryError: Java heap space` — typically caused by memory leaks or undersized heap. The critical distinction: the reference is on the stack; the object is on the heap — the variable holds a pointer, the object lives elsewhere. Primitives are stored by value on the stack; String literals live in the String pool on the heap (since Java 7; was PermGen before). Metaspace (Java 8+, replacing PermGen) holds class metadata in native memory — not heap — and has its own OOM (OutOfMemoryError: Metaspace), usually from classloader leaks. Pair each error with its area: StackOverflowError = stack = recursion bug; OutOfMemoryError: Java heap space = heap = leak or undersized; OutOfMemoryError: Metaspace = classloader leak; OutOfMemoryError: Direct buffer memory = NIO native buffers. Always enable `-XX:+HeapDumpOnOutOfMemoryError` in production, set -Xms == -Xmx to avoid resize, and use -XX:MaxRAMPercentage for containers (Java 10+). For performance, the JIT's escape analysis can sometimes stack-allocate short-lived objects to skip GC entirely — transparent to the programmer. Java 21 virtual threads (Project Loom) move the stack to the heap as a resizable continuation stack, removing per-thread overhead and enabling millions of concurrent threads. The mental model: stack = fast per-thread method workspace; heap = shared object graveyard managed by GC. Knowing what lives where, what error maps to which area, and the relevant tuning flags is foundational to all JVM diagnosis.
Heap vs Stack Memory in the JVM — Lifetime, Errors, and What Lives Where
Direct Answer
Stack is per-thread, stores method frames (local variables, parameters, partial results, return addresses), grows and shrinks with method calls/returns, has a fixed maximum size per thread (default 512KB–1MB, configurable via -Xss), and exhaustion throws StackOverflowError (typically from deep/infinite recursion). Heap is shared by all threads, stores all objects and arrays (everything created with new), is managed by the garbage collector, has configurable size (-Xms initial, -Xmx max), and exhaustion throws OutOfMemoryError: Java heap space (typically from object leaks or undersized heap). What lives where: Stack holds primitives as values (int x = 5), object references as pointers (the variable holds an address; the object itself is on the heap), method-call frames (one per active call). Heap holds all objects including String literals (in the String pool, which has been on the heap since Java 7, no longer in PermGen), arrays, lambdas, and closures. Special area: Metaspace (Java 8+) holds class metadata (replaced PermGen) and uses native memory — not heap. Lifetime: stack data lives until the method returns; heap objects live until no live references remain (GC reclaims them). Escape analysis (a JIT optimization) can sometimes allocate short-lived objects on the stack to skip GC, but this is invisible to the programmer. For interviews: pair the concept with the error — 'StackOverflowError = stack exhaustion = recursion bug; OutOfMemoryError = heap exhaustion = leak or undersized heap'.
Common Mistakes & Pitfalls
Saying String literals live on the stack (they're in the String pool on the heap since Java 7). Confusing 'reference is on the stack' with 'object is on the stack'. Forgetting Metaspace is in native memory, not heap
Check your understanding
Answer these without looking back at the article.
- Why is the reference on the stack but the object on the heap?
- Where do String literals live in modern Java?
- What's the difference between OutOfMemoryError: Java heap space and OutOfMemoryError: Metaspace?
- How does escape analysis affect heap allocation?
- How do Java 21 virtual threads change the stack memory model?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.