The JVM and Garbage Collection in Java, Explained
The JVM runs your compiled bytecode and manages memory for you; garbage collection automatically frees objects you no longer use. Here's how the JVM works, the heap vs stack, generational GC, and whether Java can still leak memory.
The JVM (Java Virtual Machine) runs your compiled bytecode and manages memory for you, and garbage collection is the part that automatically frees objects you’ve stopped using. This is why Java is “write once, run anywhere” and why you rarely think about memory — but understanding what happens underneath is exactly what separates job-ready from senior and staff level.
Here’s how Java actually runs, where memory lives, and how garbage collection works — without the JVM-tuning rabbit hole.
How Java runs: source → bytecode → JVM
Java is both compiled and interpreted:
javaccompiles your.javasource into bytecode (.classfiles) — a platform-independent instruction set.- The JVM reads that bytecode and executes it on whatever OS it’s running on.
- The JIT (Just-In-Time) compiler watches for “hot” code that runs often and compiles it to native machine code on the fly, so long-running Java gets faster as it runs.
Hello.java --javac--> Hello.class (bytecode) --JVM + JIT--> runs on any OS
That bytecode-plus-JVM layer is the whole “write once, run anywhere” promise: the same .class runs on Windows, Linux, or macOS.
Where memory lives: stack vs heap
The JVM splits memory into regions; two matter most.
| Stack | Heap | |
|---|---|---|
| Holds | method frames, local variables, references | all objects |
| Lifetime | freed automatically when a method returns | managed by garbage collection |
| Speed | very fast | slower, larger |
void example() {
int x = 5; // primitive — on the stack
String s = new String("hi"); // reference `s` on stack, object on heap
} // stack frame gone; the heap object awaits GC once unreachable
Primitives and references sit on the stack; the objects those references point to sit on the heap. When a method returns, its stack frame is cleared instantly. Heap objects stick around until garbage collection reclaims them.
What garbage collection does
The garbage collector’s job: find heap objects your program can no longer reach and free their memory. An object is reachable if something live (a local variable, a static field, another reachable object) still references it. Once nothing does, it’s garbage.
User u = new User("Ana"); // object reachable via u
u = null; // now unreachable → eligible for GC
You never call free() — you simply stop referencing an object, and the GC reclaims it later. This eliminates whole categories of bugs (dangling pointers, double frees) that haunt manual memory management.
Generational garbage collection
Modern JVMs exploit a key observation: most objects die young. So the heap is split into generations:
- Young generation — new objects. Collected often and cheaply (a “minor GC”). Most objects never leave here.
- Old generation — objects that survive several collections. Collected less often (a “major GC”).
This generational strategy makes GC fast in the common case. Modern collectors like G1 (the default) and ZGC aim for very short pauses, even on large heaps — a frequent staff-interview topic.
Yes, Java can still leak memory
A common misconception is that GC makes leaks impossible. It doesn’t — it changes their shape. A Java memory leak happens when you unintentionally keep references alive:
- Objects piling up in a static collection that’s never cleared.
- A cache with no eviction policy.
- Listeners or callbacks registered but never removed.
The objects stay reachable, so the GC can’t touch them, and memory grows. The fix is to release references you no longer need.
Common misconceptions
- “GC makes Java leak-proof.” No — unreachable only. Held references still leak.
- “
System.gc()forces collection.” It’s only a suggestion; the JVM may ignore it. Don’t rely on it. - “You must tune the GC.” Almost never for normal apps — the default G1 collector is good. Tuning is a measured, last-resort, staff-level activity.
- “Objects live on the stack.” Only primitives and references do; the objects themselves are always on the heap.
static List that you keep add()-ing to and never clear is the textbook Java memory leak — every element stays reachable forever. Static collections and unbounded caches are the usual culprits.
Where this fits
The JVM and GC are Phase 6 — the advanced layer — in our Java roadmap. They’re where memory, performance, and the collections you allocate all come together, and they’re squarely senior/staff interview territory.
The JVM, garbage collectors (G1, ZGC), memory model, and performance tuning are worked through in depth in Java for Staff Engineers, with the runtime model and stack-vs-heap introduced in Java in Three Months.
Understand reachability and the young/old split, and the JVM stops being a black box and becomes something you can reason about.
Frequently asked questions
What is the JVM?
The Java Virtual Machine is the runtime that executes Java. The compiler turns your .java source into platform-independent bytecode, and the JVM runs that bytecode on any operating system, managing memory and optimising hot code with a Just-In-Time compiler. 'Write once, run anywhere' comes from the JVM.
What is garbage collection in Java?
Garbage collection is the JVM automatically reclaiming memory used by objects your program can no longer reach. Instead of manually freeing memory, you let objects go out of scope and the garbage collector frees them, preventing most memory-management bugs.
What is the difference between the stack and the heap in Java?
The stack holds method call frames and local variables (including references), and is freed automatically as methods return. The heap holds all objects, and is managed by the garbage collector. Primitives and references live on the stack; the objects they point to live on the heap.
Can Java have memory leaks if it has garbage collection?
Yes. Garbage collection only frees objects that are unreachable. If you unintentionally keep references — in a static collection, a cache, or an unremoved listener — those objects stay reachable and are never collected. That is a Java memory leak, even with GC.