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
newallocates 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";xis a primitiveint. Its 4 bytes sit directly inside the current stack frame.sis a reference variable. Inside the frame it holds an arrow (a memory address). The"hello"Stringobject 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
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:
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.
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()); // NullPointerExceptionThe 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(); } // StackOverflowErrorThe fix: every recursion must reach a base case.
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
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
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.