JVM

On this page 18

Part I — Foundations · Interview reference

For Java/Kotlin/Scala backends, seniors are expected to reason about memory, GC, classloading, the memory model, and JIT behavior under production load — not just language syntax.


Architecture overview

.java/.kt → compiler → .class bytecode → Classloader → Runtime data areas
                                              ↓
                                    Interpreter / JIT (C1/C2/Graal)
                                              ↓
                                         GC + native threads

Runtime data areas (conceptual)

AreaPurpose
HeapObjects / arrays (GC-managed)
MetaspaceClass metadata (native, not old PermGen)
Java stacksPer-thread frames (locals, operand stack)
PC / native stacksExecution state; JNI
Code cacheJIT-compiled native code

Memory & GC

Generational hypothesis

Most objects die young → young gen (Eden + Survivor) + old gen. Collect young frequently; full collections cost more.

Collectors you should compare

CollectorCharacterWhen discussed
ParallelThroughput, stop-the-worldBatch
G1Region-based; balanced; common defaultGeneral services
ZGCUltra-low pause; colored pointersLarge heaps, tight p99
ShenandoahLow pauseSimilar space to ZGC

Know: pause time vs throughput vs memory footprint tradeoffs; allocation rate as a primary driver of GC pressure.

Signals of GC trouble

  • Rising p99 with GC logs showing long pauses
  • Allocation rate / object churn from profilers
  • Promotion failures / fragmentation (collector-specific)
  • Metaspace leaks from dynamic class generation

Tools vocabulary: GC logs (-Xlog:gc*), heap dumps, async-profiler, JFR, jcmd, Eclipse MAT.

Production case study (high volume)

Context: Multi-tenant SaaS API on G1 (32–64GB heaps) serving ~5–20K RPS/instance class; Black Friday allocation rate triples from JSON/ORM churn. Why seniors care: Allocation rate — not “wrong GC brand” — drives pauses; p99 user checkout failures track GC; oversized heaps without headroom for metaspace/direct/ native blow containers. Failure / symptom: Rising p99 with -Xlog:gc* showing to-space exhaustion / long remark; heap dump retained by session maps; promotion failure under load test only. Resolution: Cut allocation (reuse buffers, avoid byte[] copies, DTO churn); tune pause goals only after; consider ZGC when heap large + pause SLO tight; size -Xmx under cgroup limits; MAT dominator trees for leaks. Seen at / similar to: Netflix / LinkedIn JVM GC writeups; Minecraft/huge-heap ZGC adopters; Shopify JVM services under flash sales.


Java Memory Model (JMM) — interview critical

  • Happens-before relates writes to reads across threads
  • synchronized / unlock-lock, volatile write-read, thread start/join establish ordering
  • Data races → undefined behavior for Java (broken invariants, infinite loops optimizing assumptions)
  • Double-checked locking requires volatile (since Java 5 memory model fixes)

Link answers to thread safety.


Classloading

  • Hierarchy: bootstrap → platform → application; custom loaders for isolation (app servers, plugins)
  • Linkage: load → verify → prepare → resolve → initialize
  • Leaks: classloader holds classes → static maps → metaspace growth
  • SPI / JDBC drivers / shaded jars: version conflicts (“classpath hell”)

Production case study (high volume)

Context: Plugin-style payment processor and a fat “uber” Spring Boot jar in a bank’s card-network connector; redeploys every few hours in lower envs with hot reload experiments. Why seniors care: Classloader leaks → metaspace OOM after days of traffic; shading conflicts → NoSuchMethodError at peak, not in canaries with different classpath order. Failure / symptom: Metaspace climb; OutOfMemoryError: Metaspace; intermittent LinkageError after dependency bump. Resolution: Heap/metaspace dumps; find loader GC roots (static caches); kill hot-reload in prod; align BOM/shading; monitor metaspace used. Seen at / similar to: App-server plugin eras (WebLogic/JBoss); Kafka Connect connectors; Android multidex historical analogy for loader pain.


JIT & performance

  • Hot methods compiled; inlining critical for performance
  • Escape analysis → stack allocation / scalar replacement when safe
  • Megamorphic call sites inhibit inlining
  • Warmup: traffic before measuring peak; primed caches / JIT

See interpreter vs compiler.

Production case study (high volume)

Context: Streaming video origin auth service measures a “performance regression” every deploy for 10 minutes at millions of token checks/day. Why seniors care: Autoscaling on cold pods multiplies warmup pain; seniors distinguish JIT warmup from real code regressions with JFR compiler events. Failure / symptom: New pods flap Ready then fail SLO probes; error budget burn only on scale-out events. Resolution: Pre-warm / minimum warm instances; delay SLO burn alerts until soak; compare compiler profiles pre/post; avoid reflective megamorphic dispatch on the check path. Seen at / similar to: Netflix Warmup / Hollow-style patterns; AWS ALB+ECS scale-out; Twitter JVM performance culture historically.


Threads & runtime

  • JVM threads ≈ OS threads (Project Loom virtual threads change scaling model — know the difference)
  • Synchronizers: intrinsic locks, java.util.concurrent, lock-free atomics
  • Pinning risks with virtual threads + native frames / long synchronized (awareness)

Java under the hood — locks & virtual threads

MechanismImplementation sketch
Intrinsic lock (synchronized)Object header mark word: biased/thin lock → inflated heavyweight monitor on contention; bytecodes monitorenter / monitorexit
ReentrantLock / j.u.cAbstractQueuedSynchronizer (AQS): CLH-style wait queue, park/unpark
AtomicsCAS via Unsafe historically / VarHandle today; LongAdder stripes to reduce false sharing
Platform thread1:1 with OS thread (pthread); ~MB-order stack reservation
Virtual thread (Loom)Continuation mounted on a carrier platform thread (default ForkJoinPool); cheap block = unmount continuation
PinningCarrier stuck if virtual thread blocks inside synchronized or native/JNI frames — prefer ReentrantLock, short critical sections

Interview depth: explain why virtual threads help blocking IO scale, and why they do not multiply CPU cores. Link to threads and thread safety.

Production case study (high volume)

Context: Migrating a Tomcat/servlet payments API from platform threads to virtual threads to survive Black Friday connection concurrency. Why seniors care: Pinning inside synchronized JDBC wrappers or legacy monitors reintroduces carrier starvation; unbounded virtual threads still stampede the DB pool. Failure / symptom: Carriers stuck; JFR pinning events; DB connection wait timeouts despite “millions of cheap threads.” Resolution: Short critical sections / ReentrantLock; bound concurrency with semaphores matching Hikari max; watch pinning metrics; keep CPU-bound crypto on a fixed pool. Seen at / similar to: Early Loom adopters in JDK 21+ shops; Helidon/Micronaut/Spring virtual-thread blogs; same lesson as Go: cheap goroutines ≠ free DB.


Operational knobs (speak carefully)

ConcernLevers (examples)
Heap size-Xms -Xmx (don’t cargo-cult; measure)
GC choiceProduct defaults + load test
OOMHeap vs Metaspace vs native / direct buffers
Direct memoryNetty / NIO; capacity limits
Container awarenesscgroup memory limits vs heap

Senior habit: change one variable, load test, read GC + latency charts.


What interviewers probe

  1. Explain G1 vs ZGC at tradeoff level.
  2. Diagnose a memory leak approach (heap dump → dominators → GC roots).
  3. volatile vs synchronized
  4. Why equals/hashCode matter for maps; mutability hazards.
  5. Classpath / shading conflicts in microservices builds.
  6. Virtual threads: what problem they solve; what they don’t (CPU-bound).
  7. Finalizers / cleaners — why finalizers are discouraged.
  8. Warmup vs GC vs pinning — structure a Black Friday incident narrative.

Senior-level expectation: Production war stories structured as symptom → metric → root cause → fix → prevention. Accurate JMM language.


Pitfalls

  • Setting max heap = container limit (no room for metaspace, threads, direct, OS)
  • Ignoring allocation rate while chasing “GC algorithm”
  • Relying on System.gc()
  • Synchronizing on public/interned objects (deadlock / DoS)
  • Assuming “Java is slow” without profiling (often IO, DB, locks)
  • Virtual threads without bounding downstream pools
  • Declaring regressions during JIT warmup windows after scale-out

Cross-references

Interview reference — explanation quality and judgment, not syntax memorization.