Advanced⏱️ 12 min📘 Topic 22 of 22

🧠 JVM Internals — Memory Model, Heap, Stack and Garbage Collection

Master JVM internals — heap vs stack, the JVM memory model, garbage collection (generational GC, G1, ZGC), class loading, JIT compilation and memory leaks in Java. With interview Q&A.

Understanding the JVM separates senior Java engineers from the rest. It manages memory so you don't call free() — but you must understand how.

🗄️ Runtime memory areas

  • Heap — all objects live here, shared across threads. Garbage-collected.
  • Stack — one per thread; holds method frames, local variables, and references. Auto-freed when a method returns.
  • Metaspace — class metadata (replaced PermGen in Java 8).
  • PC register & native stack — per-thread execution bookkeeping.

♻️ Garbage collection

The GC reclaims objects no longer reachable from roots (stack variables, statics). Java uses generational GC:

  • Young generation (Eden + survivor spaces) — new objects; minor GC is fast and frequent.
  • Old generation — long-lived objects; major GC is slower.

Most objects die young — the generational hypothesis makes GC efficient.

⚙️ GC algorithms

  • G1 GC — default since Java 9; low-pause, region-based.
  • ZGC / Shenandoah — ultra-low-pause (sub-millisecond) for huge heaps.
  • Serial / Parallel — simpler, throughput-focused.

🔥 JIT compilation

The Just-In-Time compiler watches running bytecode and compiles hot methods to optimized native code — why long-running Java is fast.

🚰 Memory leaks in Java?

Yes — GC only collects unreachable objects. Leaks happen when you unintentionally keep references (static collections that grow forever, unclosed resources, listeners never removed).

💻 Code Examples

Stack vs heap

void demo() {
  int x = 5;                 // x on the stack
  int[] arr = new int[1000]; // 'arr' ref on stack, array on heap
}  // x and arr ref popped; array eligible for GC
Output:
Locals live on the stack; objects live on the heap.

Eligible for garbage collection

StringBuilder sb = new StringBuilder("data");
sb = null;   // the old StringBuilder is now unreachable -> GC eligible
System.gc(); // a suggestion, not a guarantee
Output:
Nulling the only reference makes the object collectable.

A classic memory leak

static final List<byte[]> CACHE = new ArrayList<>();
void handle() {
  CACHE.add(new byte[1_000_000]); // never removed -> grows forever
}
Output:
Static collection that only grows = memory leak.

⚠️ Common Mistakes

  • Believing Java can't leak memory — it can, via lingering references (growing static collections, unremoved listeners).
  • Calling System.gc() expecting immediate collection — it's only a hint; the JVM decides.
  • Confusing stack and heap — local primitives/references are on the stack; objects are always on the heap.
  • Tuning GC flags blindly — measure with a profiler first; the default G1 is good for most apps.

🎯 Interview Questions

Real questions asked at top product and service-based companies.

Q1.What's the difference between heap and stack memory?Beginner
The heap stores all objects and is shared across threads and garbage-collected. The stack is per-thread, holds method frames with local variables and references, and is automatically freed when methods return.
Q2.How does garbage collection work in Java?Intermediate
The GC identifies objects unreachable from GC roots (stack locals, static fields) and reclaims their memory. Java uses generational GC: objects start in the young generation (fast minor GC) and survivors are promoted to the old generation (slower major GC).
Q3.What is the generational hypothesis?Intermediate
The observation that most objects die young. GC exploits this by collecting the small young generation frequently and cheaply, while the old generation (long-lived objects) is collected rarely — making overall GC efficient.
Q4.Can a Java application have a memory leak?Advanced
Yes. GC only frees unreachable objects, so a leak occurs when you unintentionally retain references — e.g., objects added to a static/growing collection, caches without eviction, or listeners never deregistered. The objects stay reachable and never get collected.
Q5.What does the JIT compiler do?Advanced
The Just-In-Time compiler profiles running bytecode and compiles frequently executed (hot) methods into optimized native machine code at runtime, applying inlining and other optimizations — which is why long-running Java approaches native performance.

🧠 Quick Summary

  • Heap = objects (GC'd, shared); stack = per-thread frames/locals.
  • Generational GC: young gen (frequent minor GC) + old gen (rare major GC).
  • G1 is the default; ZGC/Shenandoah for ultra-low pause.
  • JIT compiles hot bytecode to native code at runtime.
  • Java CAN leak memory via lingering references; System.gc() is only a hint.