Skip to content
Part IA Michaelmas Term

The Stack and the Heap

The Stack

The stack is a region of memory that uses LIFO (last-in-first-out) discipline. Each thread in a Java program has its own private stack — stacks are never shared between threads.

When a method is called, the JVM pushes a stack frame onto the top of the calling thread’s stack. This frame contains:

  • Local variables: primitives (int, double, boolean, etc.) are stored directly in the frame. For reference types, the frame stores the reference (a pointer to a heap object), not the object itself.
  • Return address: where execution should resume when the method returns.
  • Operand stack: intermediate values used during expression evaluation within the method.

When the method returns (normally or via an exception), its frame is popped off the stack and the memory is immediately reclaimed. No garbage collector is involved — the stack cleans up after itself automatically.

Key properties of the stack:

  • Fixed size: each thread’s stack has a fixed size set at JVM startup (configurable with -Xss). Because the size is bounded, deep or unbounded recursion leads to a StackOverflowError.
  • Fast: allocation and deallocation are simply pointer arithmetic — moving the stack pointer up to allocate a frame and down to free it. No scanning, no compaction, no free-list management.
  • Thread-confined: data on a stack is only visible to the owning thread.

The Heap

The heap is a region of memory shared across all threads in the JVM process. Every object created with the new keyword is allocated on the heap.

Key properties of the heap:

  • Shared: all threads can access heap objects (subject to reachability through references on their stacks).
  • Managed by garbage collection: unlike the stack, heap objects are not freed when the method that created them returns. An object lives until it becomes unreachable — meaning no live thread holds a reference to it. The garbage collector (GC) periodically identifies and reclaims unreachable objects.
  • Slower allocation: allocating on the heap involves more work than a stack push. The JVM uses sophisticated techniques (thread-local allocation buffers, bump-pointer allocation) to make it fast, but it is still fundamentally more expensive than stack allocation.
  • Configurable size: the maximum heap size is set with -Xmx. The heap can grow up to this limit; if it runs out, OutOfMemoryError is thrown.
  • Not bounded by call depth: objects on the heap persist across method returns, which is why you can return an object from a method and have the caller use it.

What Goes Where

ItemLocation
Local int, double, boolean variablesStack (in the frame)
Local reference variables (e.g. String s)Stack (in the frame)
The String object itselfHeap
Instance fields (inside an object)Heap (inside the object)
Static fieldsHeap (in a special area called the method area / metaspace)
Method codeMethod area / metaspace
Arrays (even of primitives)Heap

Stack frames pointing to heap objects

Why This Distinction Matters

Parameter passing: when you pass an argument to a method, what gets copied into the callee’s stack frame is a primitive value or an object reference — never the object itself. This is fundamental to understanding Java’s pass-by-value semantics.

Recursion: each recursive call pushes a new frame. If the recursion is deep enough, the stack overflows even if the heap has plenty of space. Iterative solutions (loops) reuse the same frame and avoid this problem.

Garbage collection: the GC only manages the heap. Stack variables are the roots of reachability — if a reference to a heap object exists only on a popped stack frame, the object becomes eligible for collection (assuming no other references exist).

Escape analysis: the JVM may optimise short-lived objects by allocating them on the stack instead of the heap if it can prove they don’t “escape” the method. This is a JIT-compiler optimisation, not a language feature.