PrepZone Logo
PrepZone

Stack and Heap

Which memory region holds your variable, which holds the object, and why that distinction matters.

Why this matters

  • StackOverflowError and OutOfMemoryError are both memory failures, and the one you get tells you exactly which region filled up.
  • Thread safety only exists as a concern because stacks are private and the heap is shared.
  • Every later topic — garbage collection, memory leaks, escape analysis — assumes you can place a given value in the right region.

The two regions

Stack — per thread
main() frameuser → reference
greet() framecount = 3 (primitive)

Popped the moment a method returns.

Heap — shared
User objectname = "Asha"
String object"Asha"

Cleared by the garbage collector, not by returning.

The variable sits in the thread's stack frame. The object it names lives in the shared heap, which is why two variables can point at one object.
AspectStackHeap
HoldsMethod frames: locals, parameters, references, return addressEvery object and array, plus their instance fields
SharedOne private stack per threadOne heap shared by all threads
AllocationPush a frame on call, pop on returnAllocated by new, reclaimed by the collector
SizeSmall — around 512 KB to 1 MB per threadLarge — hundreds of megabytes to gigabytes
LifetimeExactly the duration of the method callUntil no reference can reach the object
SpeedVery fast — a pointer bumpSlower, and subject to collection pauses
When exhaustedStackOverflowErrorOutOfMemoryError
  • Holds

    StackMethod frames: locals, parameters, references, return address
    HeapEvery object and array, plus their instance fields
  • Shared

    StackOne private stack per thread
    HeapOne heap shared by all threads
  • Allocation

    StackPush a frame on call, pop on return
    HeapAllocated by new, reclaimed by the collector
  • Size

    StackSmall — around 512 KB to 1 MB per thread
    HeapLarge — hundreds of megabytes to gigabytes
  • Lifetime

    StackExactly the duration of the method call
    HeapUntil no reference can reach the object
  • Speed

    StackVery fast — a pointer bump
    HeapSlower, and subject to collection pauses
  • When exhausted

    StackStackOverflowError
    HeapOutOfMemoryError

Private and automatic versus shared and collected. Those two differences explain everything else.

Placing a value

Java
public class Demo {
    private int instanceField = 10;          // heap, inside the Demo object
    private static int staticField = 20;      // heap, with the class metadata

    public void process(int parameter) {      // stack — a copy of the argument
        int local = 5;                         // stack — the value itself
        String text = "hello";                 // stack holds the reference; the String is in the pool
        int[] numbers = new int[3];            // stack holds the reference; the array is on the heap
        Demo other = new Demo();               // stack holds the reference; the object is on the heap
    }                                          // the whole frame is popped here
}

The rule, stated precisely

  • A primitive local stores its value directly in the frame.
  • A reference local stores an address in the frame; the object it names is on the heap.
  • Instance fields live inside the heap object, whatever their type — an int field is on the heap.
  • Static fields live with the class, in metaspace, and are reachable from every thread.
  • The frame disappears the moment the method returns. The objects it referenced survive if anything else still reaches them.

Frames and the call chain

Each method call pushes a frame. Each return pops one. The chain of frames is precisely what a stack trace prints.

Java
void a() { b(); }
void b() { c(); }
void c() { throw new IllegalStateException("here"); }

// Stack when the exception is thrown, top first:
//   c()   ← throw site
//   b()
//   a()
//   main()

Unbounded recursion never pops, so the stack fills:

Java
static int recurse(int n) {
    return recurse(n + 1);        // StackOverflowError after a few thousand frames
}

The depth you reach depends on the frame size and the thread's stack size, set with -Xss. Raising it delays the error; it does not fix a missing base case.

Why thread safety exists

Java
public class Counter {
    private int count = 0;                  // heap — shared by every thread

    public void increment() {
        int local = count;                   // stack — private to this thread
        local = local + 1;
        count = local;                        // writes back to shared memory
    }
}

Two threads each have their own local. Both may read count as 5, compute 6, and write 6. One increment is lost. The locals were never the problem; the shared heap field was.

This is the reason a method using only locals is automatically thread-safe, and why anything touching instance or static state needs thought.

Escape analysis

The JIT compiler can sometimes prove an object never leaves the method that created it, and then allocate its fields directly in the frame — scalar replacement — avoiding the heap entirely.

Java
public double distance(double x1, double y1, double x2, double y2) {
    Point from = new Point(x1, y1);       // may never reach the heap at all
    Point to = new Point(x2, y2);
    return from.distanceTo(to);
}

Because neither Point is stored, returned or passed to unknown code, the JIT may eliminate both allocations. This is why writing small short-lived helper objects for clarity is usually free in practice.

Reading the two errors

AspectErrorWhat it means and where to look
StackOverflowErrorA thread's stack is fullUnbounded recursion, or mutually recursive calls
OutOfMemoryError: Java heap spaceToo many live objectsA leak, an unbounded cache, or a genuinely undersized heap
OutOfMemoryError: MetaspaceToo many loaded classesRepeated redeployment, or heavy dynamic proxy generation
OutOfMemoryError: unable to create native threadThe OS refused another threadThousands of threads; each needs its own stack
  • StackOverflowError

    ErrorA thread's stack is full
    What it means and where to lookUnbounded recursion, or mutually recursive calls
  • OutOfMemoryError: Java heap space

    ErrorToo many live objects
    What it means and where to lookA leak, an unbounded cache, or a genuinely undersized heap
  • OutOfMemoryError: Metaspace

    ErrorToo many loaded classes
    What it means and where to lookRepeated redeployment, or heavy dynamic proxy generation
  • OutOfMemoryError: unable to create native thread

    ErrorThe OS refused another thread
    What it means and where to lookThousands of threads; each needs its own stack

The message after the colon is the most useful part. Read it before changing any flag.

The last row connects back to the first column: each thread needs its own stack, so thread count and stack size multiply. Ten thousand threads at 1 MB each is 10 GB of stack memory before a single object is allocated — which is precisely the constraint virtual threads were designed to remove.

Common misreadings

  • "Primitives are always on the stack." A primitive local is. An int instance field lives on the heap inside its object.
  • "The reference and the object are in the same place." The reference is in the frame; the object is on the heap. This is the whole distinction.
  • "Each thread gets its own heap." Only stacks are per-thread. One shared heap is why synchronisation exists.
  • "Strings are on the stack because they look like values." String is an object. The pool is a region of the heap.
  • "Raising -Xss fixes a StackOverflowError." It delays it. Fix the recursion.
  • "OutOfMemoryError always means a leak." It may mean an undersized heap or a legitimately large working set. The heap dump tells you which.

Quick recall

Everything you need if you only revisit this box.

  • Stack = per-thread frames holding locals, parameters and references. Heap = shared, holds every object and array.
  • A reference local lives on the stack; the object it names lives on the heap. Instance fields live inside the heap object.
  • Frames are pushed on call and popped on return, which is exactly what a stack trace shows.
  • StackOverflowError means runaway recursion; OutOfMemoryError means too many live objects.
  • Thread safety is a concern only because the heap is shared — code using just locals is inherently safe.
  • Escape analysis may keep short-lived objects off the heap, which makes small helper objects effectively free — but do not design around it.
  • Every thread needs its own stack, so thread count times stack size is real memory.

Test yourself

Answer these before moving on — recall is what makes it stick.