Phase 06 — JVM Internals & Performance (Months 8–9)

This is the phase where you stop treating the JVM as a black box. You learn what happens between javac and a stack trace: how classes load, how bytecode is compiled to native code, how garbage collectors free memory without stopping the world (usually), and how to find out — with hard evidence — why a service is slow, allocating like crazy, or falling over with OutOfMemoryError.

Senior engineers get paid for this. Not because it comes up every day, but because when it does come up, the business is on fire and everybody looks at you. The right answer isn’t “restart it and add more heap.” The right answer is a flame graph, a GC log excerpt, and a one-line explanation of what the JVM was actually doing.

You’ll spend roughly 5–6 weeks here. Slower than earlier phases, because the mental model is dense and the tooling requires practice. Do not rush.

Weekly Schedule

Week

Focus

Deliverable

33

JVM architecture, class loading, bytecode reading with javap -c

Notes + one bytecode dissection of a switch expression

34

Memory layout, generational GC, choosing a collector

Read three real GC logs; annotate them

35

JIT tiers, inlining, deopts, GraalVM native-image trade-offs

-XX:+PrintCompilation on your Phase 5 rate limiter

36

jcmd, jstack, jmap, JFR + JMC, Async Profiler, Eclipse MAT

Record a 60s JFR on your rate limiter; open in JMC

37

JMH — lifecycle, @State, @Fork, blackholes, reading output

Five JMH benchmarks (Project B)

38

Production pathologies: OOM species, long pauses, thread starvation

Project C: three OOMs, three fixes, three heap dumps

Files in This Phase

File

What You’ll Learn

01_jvm_architecture.md

Class loaders, bytecode, tiered compilation, stack vs heap vs Metaspace

02_memory_and_gc.md

Heap regions, generational hypothesis, G1/ZGC/Shenandoah, tuning flags, reading GC logs

03_jit_and_optimization.md

Inlining, escape analysis, deoptimization, JIT logs, GraalVM AOT

04_diagnostic_tools.md

jcmd, jstack, jmap, jstat, JFR, JMC, Async Profiler, Eclipse MAT

05_benchmarking_with_jmh.md

Why hand-rolled benchmarks lie; JMH lifecycle, blackholes, interpreting output

06_common_production_pathologies.md

OOM species, long GC pauses, thread starvation, connection pool exhaustion, container awareness

projects.md

Profile a slow service; five JMH benchmarks; deliberately cause and fix three OOMs

Exit Criteria

You’ve cleared this phase when you can honestly tick every box:

  • You can read a javap -c bytecode listing for a small method and explain each opcode.

  • You can name every region of a G1 heap and describe what triggers a Young vs Mixed vs Full GC.

  • You can pick G1, ZGC, or Parallel for a given workload and defend the choice with two numbers.

  • You’ve captured a 60-second JFR recording and read the resulting flame graph in JMC.

  • You’ve used Async Profiler for CPU, allocation, and lock profiling on the same app.

  • You’ve analysed a heap dump in Eclipse MAT and identified a dominator.

  • You’ve written a JMH benchmark that uses Blackhole.consume(...) correctly and reported score ± error.

  • You can diagnose a heap OOM, a Metaspace OOM, and a direct-buffer OOM from the error message alone.

  • You can explain -XX:MaxRAMPercentage and why it matters in Kubernetes.

⚠️ What Most People Get Wrong

Two failure modes to avoid before you write a single line:

1. Cargo-cult GC flags. You will find blog posts from 2014 recommending flags that are removed, renamed, or actively harmful on modern JVMs. -XX:+UseConcMarkSweepGC is gone in Java 14+. -XX:+UseParNewGC is gone. -XX:PermSize and -XX:MaxPermSize were dropped in Java 8 (there’s no PermGen anymore). Rule: flags older than the JVM you’re running are suspect. Consult the JEP that introduced the collector, not a random Medium post.

2. Benchmarks that measure the wrong thing. A five-line main with System.nanoTime() around a loop is not a benchmark — it’s a coin toss. JIT hasn’t warmed up, dead-code elimination erased half your work, and constant-folding did the rest. If you show a colleague timing numbers that were not produced by JMH (or a comparable framework), you should expect to be politely ignored. Learn JMH properly in file 05.

Prerequisites

You should have shipped Phase 05 in some form (at least Projects A and B). GC and JIT questions get real once you have a live executor pool to observe.

You also need a JDK 21+ install with jcmd, jstack, jmap, jinfo, jstat, and jfr on your PATH. On macOS with Homebrew: brew install --cask temurin@21. Verify with jcmd -h. Async Profiler is a separate download; install it now: curl -L -o async-profiler.tar.gz https://github.com/async-profiler/async-profiler/releases/latest/download/async-profiler-3.0-macos.tar.gz.


Return to ../README.md · Next: 01_jvm_architecture.md