Skip to content

Guide

What is the difference between the heap and the stack?

Where Java objects and method calls live: the shared heap, each thread’s JVM stack and its frames, references versus objects, and the misconceptions to avoid.

Heap

JVM specification

Shared memory for all class instances and arrays, reclaimed automatically by the garbage collector.

Simply put

The big shared memory area where objects live. new Person("Alex") creates a Person in the heap; variables only hold references to it. You never free heap memory yourself: the garbage collector reclaims objects nothing refers to any more.

What a developer sees

Every new, and every array, allocates in the heap. Size it with -Xms (initial) and -Xmx (maximum); on a server-class machine HotSpot JDK 25 uses G1 and a default maximum of a quarter of physical memory.

Objects stay alive while they are reachable: a static List that only ever grows keeps everything in it alive, a classic memory leak. When the heap is full of live objects you get OutOfMemoryError: Java heap space; -XX:+HeapDumpOnOutOfMemoryError writes a heap dump you can analyse.

Picture it

A big shared warehouse. Each object is a box on a shelf, and your variables are claim tickets that say where to find a box.

The warehouse

The heap

A box

An object or an array

A claim ticket

A reference, held by a variable or a field

Staff who clear boxes no ticket points to

The garbage collector

Where the picture stops working

A ticket is not a shelf number you can do arithmetic with: references are opaque, and many collectors move boxes and quietly update every ticket. Boxes that no ticket reaches are not removed at once, only when the collector runs.

Deep dive: the specification and HotSpot in detail

JVMS §2.5.3: created at start-up and shared among all threads; the area from which memory for all class instances and arrays is allocated. Storage is reclaimed by automatic storage management; objects are never explicitly deallocated. Size may be fixed or expandable; if more heap is needed than can be provided, the JVM throws OutOfMemoryError.

HotSpot JDK 25 on a server-class machine uses G1 by default: the heap is split into equal-sized regions (2 MB in our measured run) that serve as eden, survivor, old or humongous regions. Threads allocate by bumping a pointer inside a private . By default an object has a 12-byte header (8-byte mark word + 4-byte compressed class pointer) and references are 32-bit — so a reference is not a raw memory address.

The specification requires

One shared heap, automatically managed, no layout or algorithm mandated (JVMS §2.7: "does not mandate any particular internal structure for objects").

HotSpot (JDK 25) does

HotSpot JDK 25: GC-specific layout (G1 regions by default), TLAB allocation, 12-byte headers and compressed oops by default.

Object layout on JDK 25 (x64, default flags)Output
Person.name field offset          = 12   (8-byte mark word + 4-byte class pointer)
int[] first element offset        = 16   (12-byte header + 4-byte length)
with -XX:+UseCompactObjectHeaders = 8 and 12 (JEP 519: 8-byte header)

Verified with OpenJDK 25.0.4 · Linux x64, measured with Unsafe.objectFieldOffset / arrayBaseOffset

Common misconceptions

  • Every `new` allocates an object in the heap.

    Semantically yes, but if the JIT proves an object never escapes its method, can replace it with plain local values (scalar replacement), so no heap object exists.

  • A Java reference is the memory address of the object.

    The specification only says references point to objects. HotSpot often stores 32-bit compressed oops (offsets scaled by 8), and moving collectors change addresses while the program runs.

  • The Java heap is the same as the C heap (`malloc`).

    They are different regions of process memory. The JVM reserves the Java heap itself and manages it with its GC.

Sources

JVM stack

JVM specification

Per-thread stack of frames: one frame per active method call, pushed on call, popped on return.

Simply put

Each thread's call history. When a method is called, a new frame is placed on top; when it returns, that frame is removed. The frame on top is the method running right now.

What a developer sees

Every thread has its own stack. Calling a method pushes a frame; returning, normally or by throwing, pops it. A stack trace is a picture of those frames when the exception was created, newest first.

Unbounded recursion keeps pushing frames until the stack is full and the JVM throws StackOverflowError. -Xss sets the stack size for new threads (1 MB by default on Linux x64 in our measurements).

Picture it

A pile of plates: you can only put a plate on top, or take the top one off.

A plate

A frame: one method call in progress

Putting a plate on top

Calling a method

Taking the top plate off

The method returning

The top plate

The method running right now

Where the picture stops working

Each thread has its own pile, and the plates are not all the same size: each frame is as big as its method needs. A real stack also has a maximum size; one plate too many is a StackOverflowError.

Deep dive: the specification and HotSpot in detail

JVMS §2.5.2: each thread has a private JVM stack, created with the thread, that stores frames and is manipulated only by pushing and popping them. Its size may be fixed or dynamic. If a computation needs a deeper stack than permitted, the JVM throws StackOverflowError; if the stack cannot be created or expanded, OutOfMemoryError.

HotSpot uses the thread's native stack (-Xss; ThreadStackSize defaults to 1024 KB on Linux x64). Protected guard zones at the end of the stack (reserved, yellow and red pages, plus a shadow zone checked on method entry) turn an overflow into a recoverable StackOverflowError instead of a crash.

The specification requires

A per-thread stack of frames with defined overflow errors.

HotSpot (JDK 25) does

HotSpot: the native thread stack, sized by -Xss, with guard pages that detect overflow.

Overflowing the default 1 MB stackJava
public class Deep {
    static int depth = 0;
    static void recurse() { depth++; recurse(); }

    public static void main(String[] args) {
        try { recurse(); } catch (StackOverflowError e) {
            System.out.println("StackOverflowError at depth ~" + depth);
        }
    }
}

Verified with OpenJDK 25.0.4 · Linux x64; the depth varies with frame size and JIT compilation

Common misconceptions

  • Objects created in a method are stored on that method's stack frame.

    Objects are allocated in the heap; the frame holds only references to them. (The JIT may avoid an allocation entirely through scalar replacement, but that is an optimization, not the model.)

Sources

JVM Atlas

Explore the machine behind Java programs. A free, open-source atlas of the Java Virtual Machine. Specification baseline: Java SE 25. Implementation details: OpenJDK HotSpot, JDK 25.

MIT licensed · no accounts, no tracking, no backend · your progress stays in your browser.

JVM Atlas is an independent educational project and is not affiliated with or endorsed by Oracle. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.