Dataslope logoDataslope

The Stack and the Heap

The two memory regions every Java programmer thinks about — and what really happens when you call a method or create an object

You can write Java for years without thinking about memory. But the moment you hit a tricky bug — a NullPointerException, an unexpected shared mutation, a stack overflow — a mental model of the stack and the heap turns confusion into clarity.

The two regions, side by side

  • Stack: holds method call frames. Each frame contains the parameters and local variables of one call. Strictly last-in-first-out: when a method returns, its frame disappears.
  • Heap: holds objects. Every new allocates here. Objects outlive the method that created them, as long as some reference still points at them.

Primitives live on the stack; objects live on the heap

When you write:

int    x = 42;
String s = "hello";
  • x is a primitive int. Its 4 bytes sit directly inside the current stack frame.
  • s is a reference variable. Inside the frame it holds an arrow (a memory address). The "hello" String object itself lives on the heap.

Two different variables can hold the same arrow — they then refer to the same heap object. That is aliasing, and it is the single most important thing to picture about heap memory.

Aliasing demonstrated

Code Block
Java 8 (Update 492)

b = a did not copy the list. It copied the reference. Now a and b both point at the same heap object. Adding via b shows up via a.

What happens during a method call

Every method call pushes a fresh frame onto the stack. Every return pops it. Watch what happens here:

Code Block
Java 8 (Update 492)

Calling factorial(4) builds up a stack of frames:

Each frame has its own n. They do not interfere with each other. As each call returns, its frame is destroyed and its locals disappear. This is why recursion works at all.

If recursion goes too deep, the stack runs out of room and you get StackOverflowError. Try removing the base case sometime — you'll see it instantly.

Pass-by-value, always

Java is always pass-by-value. What gets copied depends on the type:

  • For a primitive parameter, the value is copied. Changing it inside the method does not affect the caller's variable.
  • For a reference parameter, the reference is copied. The method gets its own arrow pointing at the same object. Mutating the object affects what the caller sees, but reassigning the parameter does not change the caller's variable.
Code Block
Java 8 (Update 492)

Expected output:

after bumpPrimitive: n = 5
after mutate: items = [a, b]
after replace: items = [a, b]

The mental picture: every parameter is an arrow drawn fresh inside the new frame. Pointing it somewhere new does not affect the caller's arrow. Modifying the target of the arrow does.

When does heap memory get freed?

When no live reference points at an object, it becomes unreachable — and eventually, the garbage collector reclaims the space. You almost never think about this directly. You just stop holding references to things you no longer need (often by letting variables go out of scope, or by reassigning fields).

After v = new Order(2);, the original Order #1 has no incoming arrows. It will be collected on a future GC pass.

Two famous bugs explained

NullPointerException

A reference variable that holds the special value null points to no object. Dereferencing it (x.foo()) crashes with an NPE.

String s = null;
System.out.println(s.length());   // NullPointerException

The fix: don't use references that may be null without checking first, or design APIs that never return null in the first place.

StackOverflowError

A method calls itself (directly or indirectly) without a base case, pushing frames forever until the stack runs out:

static void boom() { boom(); }   // StackOverflowError

The fix: every recursion must reach a base case.

QuestionSelect one

What is stored in a stack frame for a method call?

All objects the method has ever created

The method's parameters and local variables, including primitives by value and references that point to heap objects

A copy of every object passed to the method

The bytecode of the method

QuestionSelect one

After List<String> b = a;, how many list objects exist on the heap?

Two — a and b each point to a different list

Zero — Java doesn't allocate a list yet

One — a and b are two references pointing to the same list

One, but b is an immutable copy

QuestionSelect one

A method receives a parameter of type Order and then writes order = new Order(); inside its body. What does the caller observe?

The caller's variable now points to the new Order

The original Order is silently destroyed

Nothing — Java is pass-by-value; reassigning the parameter only changes the local reference inside the method, not the caller's variable

A compile error

You now have the mental model that unlocks everything else: stack frames flow with calls, heap objects live until no one points at them, and references are arrows, not copies. Next we leave single-object thinking and start organizing collections of data.

On this page