What is the difference between stack and heap memory in Java?
Table of Contents
1. Short Answer
Java uses two main runtime memory regions: the stack and the heap. The stack stores method call frames and local variables per thread; the heap stores all objects and arrays and is shared across all threads. The garbage collector manages the heap; the stack is managed automatically by the JVM as methods enter and exit.
2. Detailed Explanation
2.1 Stack Memory
Each JVM thread has its own private stack created at thread startup. The stack is a LIFO (Last-In, First-Out) structure composed of frames. A new frame is pushed onto the stack every time a method is invoked, and popped when the method returns.
Each stack frame contains:
- Local variable array — all local variables including method parameters. Primitive types (
int,long,double,boolean, etc.) are stored directly as values here. - Operand stack — a working area the JVM uses when executing bytecode instructions.
- Reference to the constant pool — used to resolve symbolic references at runtime.
- Return address — where execution should resume after the method returns.
Stack memory is extremely fast to allocate and deallocate — allocation is just a pointer increment.
Its size is fixed at thread creation and controlled by the -Xss JVM flag (e.g., -Xss512k).
2.2 Heap Memory
The heap is a single shared region created at JVM startup. Every object you create with new
is allocated on the heap. The heap is sub-divided by the garbage collector into regions:
- Young Generation — Eden + two Survivor spaces. New objects are born here. Minor GC is frequent and fast.
- Old (Tenured) Generation — long-lived objects promoted from Young Gen. Major GC is infrequent but slower.
- Metaspace (Java 8+, replaces PermGen) — class metadata, method bytecode, static variables.
Heap size is controlled by -Xms (initial) and -Xmx (maximum) JVM flags.
2.3 Where Primitives and Objects Actually Live
This is one of the most common interview confusion points:
- A primitive local variable (e.g.,
int x = 5;inside a method) lives on the stack. - A primitive instance field (e.g.,
this.count = 5;) lives on the heap, inside the object. - An object always lives on the heap, even if referenced by a local variable.
- A local reference variable (e.g.,
String s = new String("hi");) — the referenceslives on the stack, theStringobject lives on the heap.
2.4 StackOverflowError
Thrown when the JVM cannot push another frame onto the thread's stack because the stack is full. The most common cause is unbounded recursion — a method calling itself without a base case. Each recursive call adds a new frame; eventually the stack overflows.
You can increase stack size with -Xss2m, but fixing unbounded recursion is always
the correct solution. Deep recursive algorithms should be converted to iterative ones using an
explicit stack data structure.
2.5 OutOfMemoryError
Thrown when the JVM cannot allocate a new object on the heap and the GC cannot free enough space. Common error messages and their meanings:
Java heap space— heap is exhausted; increase-Xmxor fix memory leaks.GC overhead limit exceeded— GC is running almost continuously but reclaiming very little; likely a memory leak.Metaspace— too many class definitions loaded (common with dynamic class generation); tune-XX:MaxMetaspaceSize.Direct buffer memory— NIO direct byte buffers exhausted off-heap memory.
3. Code Example with Memory Annotations
public class MemoryDemo {
// Instance field — lives on the HEAP (inside the object)
private int instanceCount = 0;
// Static field — lives in METASPACE
private static int staticCount = 0;
public void process() {
// Local primitive — lives on the STACK frame of process()
int localPrimitive = 42;
// Reference lives on the STACK; the String object lives on the HEAP
String localRef = new String("hello");
// A local array reference is on the STACK;
// the array object and its int[] elements are on the HEAP
int[] arr = new int[1000];
// This call pushes a NEW FRAME onto the stack
helper(localPrimitive);
// When helper() returns, its frame is POPPED from the stack
} // When process() returns, its frame is POPPED;
// localPrimitive is gone; localRef is gone (object may be GC'd)
private void helper(int value) {
// value is a copy — lives on this frame's STACK
System.out.println(value);
// Recursive call — each recursion adds another frame
// if unbounded: StackOverflowError
}
// Unbounded recursion example — will throw StackOverflowError
public void infiniteRecursion() {
infiniteRecursion(); // each call pushes a frame — stack fills up!
}
public static void main(String[] args) {
// 'demo' reference is on the STACK of main()
// the MemoryDemo object is on the HEAP
MemoryDemo demo = new MemoryDemo();
demo.process();
}
}
3.1 Visualizing Stack Frames During Execution
// Call sequence: main() → process() → helper()
//
// Stack (grows downward):
// +-------------------------+
// | main() frame | ← demo reference (points to heap)
// +-------------------------+
// | process() frame | ← localPrimitive=42, localRef ref, arr ref
// +-------------------------+
// | helper() frame | ← value=42 (TOP of stack)
// +-------------------------+
//
// Heap:
// +------------------------------------------+
// | MemoryDemo object (instanceCount=0) |
// | String "hello" object |
// | int[1000] array |
// +------------------------------------------+
4. When Does Each Region Apply?
| Scenario | Memory Region |
|---|---|
| Local primitive variable inside a method | Stack |
| Method parameter | Stack |
Object created with new | Heap |
| Instance field of an object | Heap (inside the object) |
| Static field | Metaspace |
| Class metadata / bytecode | Metaspace |
| String literals | Heap (String Pool in Heap since Java 7) |
5. Interview Context
Interviewers ask this question to assess your understanding of JVM internals. Strong answers:
- Correctly state that objects always live on the heap (not that "objects live where they're declared").
- Mention thread-safety implications: the stack is thread-private so stack variables don't need synchronization; heap objects do.
- Connect to GC: only heap objects are managed by GC; stack frames are reclaimed automatically on return.
- Mention
-Xssfor stack size and-Xmx/-Xmsfor heap. - Optionally mention JIT escape analysis for bonus points.
6. Common Pitfalls
- "Primitives always live on the stack" — False. Primitive local variables live on the stack, but primitive instance fields live on the heap inside their object.
- "Objects can live on the stack" — Technically possible via JIT escape analysis, but this is a transparent optimization. From the language model, objects live on the heap.
- Confusing StackOverflowError with OutOfMemoryError — Stack overflow = too many frames (usually recursion). OOM = heap exhausted. They are distinct memory spaces with distinct errors.
-
Assuming larger heap fixes StackOverflowError — It won't. Increase
-Xssfor stack size,-Xmxfor heap size.
jmap -heap <pid> to inspect heap usage. Use jstack <pid>
to see thread stacks. Use VisualVM or Java Mission Control for real-time memory monitoring.
7. Conclusion
Stack and heap are the two fundamental runtime memory regions in the JVM. The stack is fast,
thread-private, and automatically managed but limited in size — overflowing it causes
StackOverflowError. The heap is where all objects live, is managed by the garbage
collector, and is shared across threads — exhausting it causes OutOfMemoryError.
Understanding where data lives helps you write memory-efficient, thread-safe Java code and
diagnose production issues with confidence.