Skip to main content

Garbage Collection: Stack vs. Heap Memory

Hard

Interview Question: "Walk me through how Garbage Collection works in Java. What are GC Roots, how does the Generational Hypothesis divide heap memory, and how do Stack and Heap differ?"

Every thread gets its own stack: a small, private workspace for whatever method it's currently running: local variables, method parameters, and pointers to objects. When a method returns, its slice of the stack just pops off. No cleanup needed, no garbage collector involved: it's automatic and instant.

The heap is different: one shared space, used by every thread, holding every object you create with new. Nothing on the heap cleans itself up automatically, so Java runs a background process, the Garbage Collector (GC), that figures out which objects are still in use and frees the rest.

Stack vs. Heap​

FeatureThe StackThe Heap
What It StoresPrimitives, method call frames, pointers to objects.The actual objects (anything made with new).
Scope & ConcurrencyPrivate to each thread.Shared by every thread.
Memory ReclamationAutomatic, instant: pops when the frame pops.Non-deterministic: reclaimed later by the GC.
Failure ModeStackOverflowError (recursion too deep).OutOfMemoryError (heap full).

How the GC decides what's garbage: GC Roots​

Java doesn't just count references (that approach breaks the moment two objects point at each other in a cycle). Instead it asks: can this object still be reached from somewhere that's definitely alive?

Those "definitely alive" starting points are called GC Roots: things like local variables on a running thread's stack, active threads themselves, static fields, and a few native-code pointers. The GC walks outward from every root, following references. Anything it never reaches is garbage: nobody can get to it anymore, so it's safe to free.

Why the heap is split into generations​

Most objects die young: a loop variable, a temporary string, a short-lived request object. Production garbage collectors (G1, ZGC, Parallel) lean into this pattern, called the Weak Generational Hypothesis, by splitting the heap into generations instead of scanning the entire thing every time:

YOUNG GENERATION (MINOR GC)OLD GENERATION (MAJOR GC)Eden Spacenew Object()most die hereSurvivor (S0)Survivor (S1)Minor GC clears Eden; survivors age in S0/S1PROMOTIONage >= 15Tenured / Old Spacelong-lived objectssingletons, caches, poolsMajor GC: slow, Stop-The-World
New objects enter Eden. Survivors cycle through S0 and S1 with an age counter. Surviving enough cycles promotes them to the Old Generation.
  • Young Generation: New objects land in Eden. When Eden fills up, a quick Minor GC runs. Whatever survives moves into one of the two Survivor spaces (S0 and S1, alternating each cycle), and each survivor gets an age counter bumped up.
  • Old Generation: Once an object survives enough Minor GC cycles (15, by default in HotSpot), it gets promoted here - it's clearly a long-lived object, not worth re-checking every cycle. Cleaning the Old Generation is a Major GC, and it's slower: it often needs a Stop-The-World pause, where every application thread briefly freezes.

This split matters because Minor GCs - the common case - only need to scan the small Young Generation, not the whole heap. That keeps garbage collection cheap most of the time.

Does everything really go on the heap?​

Not always. The JIT compiler can perform Escape Analysis: if it can prove an object never leaves the method it was created in - never gets returned, stored somewhere external, or handed to another thread - it doesn't need heap allocation at all. It can:

  • Allocate it on the stack instead, so it disappears the instant the method returns, with zero GC involvement.
  • Scalar-replace it - break the object apart into its individual fields as plain local variables, skipping allocation entirely.

Clean Code Example​

Here is how memory lifecycle and deallocation behave across languages:

public class MemoryDemo {
public static void main(String[] args) {
// 'dog' is on the Stack; new Dog() is in the Heap (Eden Space)
Dog dog = new Dog("Buddy");

// Severing the GC Root reference:
// The Dog object is now unreachable and eligible for Minor GC
dog = null;
}
}