OOP Basics ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-07-26

Java Memory Structure: Stack and Heap

Stack and heap are the two memory areas the JVM manages while your program runs. The heap stores objects and arrays; the stack stores method call frames with local variables and the references that point to those objects. Heap memory is reclaimed by the garbage collector, while stack memory is released by the JVM itself the moment a method returns.

Memory management in Java is more involved than just "stack and heap", but these two areas explain almost everything you need at the start: how variables behave, where objects actually live, and what the garbage collector is doing.

1. Heap memory

The Java heap is the memory region the JVM allocates at startup and uses to store every object and array your application creates. Every time new is executed, the memory for that object comes from the heap.

The heap is also where the garbage collector works. It finds objects that can no longer be reached through any chain of references and frees the memory they occupied. You never delete objects manually in Java.

There is one heap per JVM, and it is shared by all application threads. An object created in one thread can be read and modified from another — which is exactly why shared objects need synchronization.

One clarification that is often stated incorrectly: an object in the heap is not "visible everywhere" by itself. You can only reach it through a reference. If no reference to the object was passed anywhere and the last one goes out of scope, the object becomes unreachable and will eventually be collected.

2. Stack memory

Stack memory in Java works on a LIFO (last-in, first-out) basis. Each time a method is invoked, a new stack frame is pushed onto the top of the stack. That frame holds:

  • local variables of primitive types (their actual values),
  • references to objects that live in the heap,
  • method parameters and bookkeeping data needed to return to the caller.

As soon as the method finishes, its frame is popped and the memory is freed instantly. The garbage collector is not involved at all.

Every thread gets its own stack. A thread's local variables are by definition invisible to other threads, which makes them inherently thread-safe. A stack is also far smaller than the heap — typically a few hundred kilobytes to a couple of megabytes per thread.

The key rule: an object is always created in the heap, and only the reference to it sits on the stack. The value of a reference variable is not the object itself but the address of that object in the heap.

Easy to get wrong

Only local primitives live on the stack. A primitive field of an object (say int x declared inside a class) is stored together with the object itself — that is, in the heap. The shortcut «primitives go on the stack» is only true for variables declared inside a method.

3. Example: what goes on the stack, what goes on the heap

Compare a primitive variable with a reference variable. When you declare int a = 10, the value 10 itself is stored in the stack frame. When you write Test a = new Test(), the object is allocated in the heap while the variable a stays on the stack and holds a reference to that object:

Java memory structure: primitive variable on the stack, object on the heap

The same idea in code:

public class MemoryDemo {

    static class Point {
        int x;   // field of the object - stored in the heap with the object
        int y;
    }

    public static void main(String[] args) {
        int a = 10;              // the value 10 lives in main's stack frame
        Point p = new Point();   // the Point object is in the heap, the reference p is on the stack
        p.x = 5;                 // mutating a field of the heap object

        modify(p);               // a copy of the reference is passed

        System.out.println(a);   // 10
        System.out.println(p.x); // 42 - the method changed the same heap object
    }

    static void modify(Point point) {  // a new stack frame
        point.x = 42;                  // point and p refer to the same object
    }                                  // the frame is popped here
}

This small program demonstrates three things at once: a primitive value is copied, a reference is copied too (Java always passes arguments by value), but the copied reference still points at the very same heap object — which is why the field change is visible in the caller.

4. Stack vs heap: side-by-side comparison

Criterion Stack Heap
What is stored Method frames: local primitives, references, parameters Objects, arrays and all object fields
Ownership One stack per thread One heap per JVM, shared by all threads
Lifetime of data Until the method returns As long as the object is reachable
How memory is freed Automatically, by popping the frame By the garbage collector
Size Small; configured with -Xss Much larger; configured with -Xms / -Xmx
Access speed Very fast Slower: reference indirection plus GC work
Error when exhausted StackOverflowError OutOfMemoryError: Java heap space

5. Metaspace and other JVM memory areas

Stack and heap are not the whole picture. A modern JVM (HotSpot) also maintains several other regions:

  • Metaspace — metadata for loaded classes and methods. Since Java 8 it lives in native OS memory, outside the heap.
  • String pool — moved into the heap in Java 7, so interned string literals can be garbage collected.
  • Code cache — the native machine code produced by the JIT compiler.
  • PC registers and native method stacks — small per-thread service areas.

Outdated advice

Statements like «class definitions are stored in the heap, in PermGen» describe Java 7 and earlier. Java 8 removed PermGen entirely: class metadata moved to Metaspace outside the heap, and the flags -XX:PermSize / -XX:MaxPermSize no longer exist — use -XX:MaxMetaspaceSize instead.

6. StackOverflowError and OutOfMemoryError

The two classic memory errors map directly onto the two memory areas.

StackOverflowError is thrown when a thread runs out of stack space. The usual cause is recursion without a proper base case: every call pushes another frame until the stack is full.

public class StackOverflowDemo {
    static int depth = 0;

    static void recurse() {
        depth++;
        recurse();   // no exit condition
    }

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

OutOfMemoryError: Java heap space is thrown when there is no room left in the heap for a new object and the garbage collector cannot free anything — for example, when a collection keeps growing and holds references to objects that are never released (a memory leak).

The sizes of these areas are set when the JVM starts:

java -Xms256m -Xmx1g -Xss512k -XX:MaxMetaspaceSize=256m MyApp

Here -Xms is the initial heap size, -Xmx the maximum heap size, -Xss the stack size of a single thread, and -XX:MaxMetaspaceSize the upper limit for Metaspace.

Frequently asked questions

Are object fields stored on the stack or in the heap?

All fields of an object, both primitive and reference ones, are stored inside the object itself, which means in the heap. Only local variables of a method and the references pointing to objects end up on the stack.

Where are static fields and class metadata stored?

Class metadata such as the class structure and method bytecode lives in Metaspace, outside the heap since Java 8. The values of static fields are held by the java.lang.Class object, which resides in the heap, and the objects they point to stay in the heap as long as the class is loaded.

Does Java pass objects by reference?

No. Java is always pass by value. For a reference type the value of the reference is copied, so the method can change the fields of the heap object, but it cannot make the caller's variable point to a different object.

Is every object really allocated on the heap?

In terms of the Java memory model, yes. In practice the JIT compiler performs escape analysis: if an object never escapes the method, it can be broken into separate fields and kept on the stack or in registers. This is a runtime optimization and does not change how you write the code.

Why is the stack thread-safe while the heap is not?

Each thread owns a private stack, so its local variables cannot be seen by other threads. The heap is shared by the whole JVM, and several threads can mutate the same object at the same time, which is why synchronized, volatile and other synchronization tools exist.

Comments

Please log in or register to have a possibility to add comment.