Rung 6 β JVM Performance Case StudyΒΆ
Target month: Shipped by end of M9 = end of March 2027 Calendar deadline: March 31, 2027 Phase alignment: Phase 06 β JVM Internals & Performance Signal level: Deep-JVM β proves you can find performance bugs, not just write them
What It IsΒΆ
A public repo + a blog post that document a real, before/after JVM performance investigation on your own Rung 5 code (or a comparable Java workload). The story is: βI had a program. It was slow. Here is exactly how I made it fast.β With numbers. With flame graphs. With commit-by-commit optimization.
This rung is deliberately structured as a case study, not a library. The artifact is the narrative β the repo is the appendix.
The workload you profileΒΆ
Use the virtual-threads HTTP-fanout benchmark from Rung 5 as your target. It is:
Real enough to have interesting bottlenecks
Small enough to fully understand
Already in your portfolio, so this rung reinforces Rung 5
If Rung 5 didnβt produce a rich-enough workload, use Artifact A (rate limiter) under contention β the sliding-window-log implementation is often quietly slow, and profiling it is instructive.
The investigation loop (5-8 iterations)ΒΆ
Each iteration is a tagged git commit + a section in the blog post:
Establish a baseline. Run the benchmark. Capture: throughput, p50/p95/p99 latency, GC pause time, heap usage, allocation rate.
Profile. Use async-profiler to capture a CPU flame graph and an allocation flame graph. Attach the
.htmloutputs to the repo.Read the graph. Identify the hot method, hot allocation site, or GC pressure source.
Form a hypothesis. Write it in the commit message before making the change.
Change one thing. One commit = one intervention. Rerun the benchmark. Capture new numbers.
Verify or reject. If the numbers improved, keep the change. If they didnβt, revert and note the negative result.
Aim for 5-8 iterations. Not every iteration needs to succeed β documented failures are as valuable as documented successes.
Suggested interventions to exploreΒΆ
(Pick 5-8; not all will apply to your workload)
Replace
Stringconcatenation in hot path withStringBuilderReplace boxed collections with primitive-specialized ones (Eclipse Collections or fastutil)
Tune GC: G1 vs ZGC vs Parallel for this workload
Increase or decrease heap size, observe GC pause impact
Escape-analysis-friendly refactor: eliminate short-lived objects
Replace
HashMapwithConcurrentHashMap(or vice versa) based on contention profileRemove
synchronizedin favor ofLongAdderwhere appropriateCache Thread.currentThread() reads across a hot loop
Replace reflection with
MethodHandleorLambdaMetafactoryTune
-XX:CompileThresholdor use-XX:+PrintCompilationdiagnosis
Where To PublishΒΆ
Repo:
github.com/RaghulR2003/jvm-perf-case-studyβ public, pinned. Consider making this a fork of your concurrency-playground repo so the narrative is preserved.Blog: Hashnode primary. Title: βMaking a Java 21 fanout service 4x faster β flame graphs, commits, and one very surprising
synchronized.β (Use your actual multiplier.)Cross-post: dev.to canonical to Hashnode. Reddit r/java once, r/programming once. Foojay for Java-audience amplification.
LinkedIn: Long-form post with the flame graph as the hero image. Highlight the most surprising finding β those are the posts that circulate.
Acceptance CriteriaΒΆ
Repo exists, public, pinned
5-8 numbered iteration commits, each with: before/after numbers in commit message, flame graphs attached, hypothesis stated
benchmarks/directory with all JMH runs,results-v0.csvthroughresults-v8.csvflame-graphs/directory with async-profiler HTML output for each iterationRepo README has a summary table: iteration, intervention, throughput before, throughput after, verdict (kept/reverted)
Blog post published with the 3 most instructive iterations documented in detail, including at least one documented negative result
All numbers include hardware + JDK build + GC + heap size (repeated from Rung 5βs discipline)
Async-profiler installed, working, documented in a
PROFILING.mdRepo demonstrates at least one GC-related intervention (GC change or allocation reduction)
Repo demonstrates at least one JIT-related insight (e.g.,
-XX:+PrintCompilationoutput analyzed)Blog post attracts 50+ views in 30 days OR 25+ reactions on LinkedIn
Signal It SendsΒΆ
You can debug production. The M13 pitch says βfrom
OutOfMemoryErrorto deployment.β This rung is theOutOfMemoryErrorhalf. study partners will read the repo and know youβve held a flame graph in your hands.You know what a flame graph means. Most Java engineers cannot read one. Publishing one, with commentary, immediately places you in the top 10%.
You know when to use ZGC. By M9, ZGC is the default recommendation for low-latency Java services. Having tuned GC for your own workload proves you understand why it matters.
You publish negative results. This is a strong senior-signal. Junior engineers only publish wins; senior engineers publish reverts because the reverts prove the process was rigorous.
You write about hard things. JVM internals blog posts have low supply and high demand. This post will accumulate SEO value for years.
Common Failure ModesΒΆ
Optimizing without profiling. βI bet if I switch to
LongAdderitβll be faster.β Maybe. Prove it with a flame graph first, then measure the change. Every intervention needs a profile as its justification.Micro-benchmarking traps. Running a tight loop 1M times without warmup, dead-code elimination, or blackhole. Every measurement uses JMH properly or it doesnβt count. This is the Rung-5 discipline continued.
Cherry-picking wins. Only publishing the interventions that worked. Read a senior-level Java performance post β the failed attempts are the interesting part. Include yours.
Skipping GC analysis. GC is often the largest lever. If your case study never touches GC, youβve dodged the hardest part of JVM performance.
Flame graph, no story. Attaching flame graphs without narrative. The graph is evidence β the reading of the graph is the artifact. Write the reading.
Running on your laptop under load. Your laptop is thermally throttled, has background processes, and shares a CPU with your browser. Use a dedicated machine or a cloud VM. Document which.
Time EstimateΒΆ
Async-profiler setup + baseline profiling: ~4 hours
5-8 iterations, each ~4 hours (change + benchmark + profile + commit): ~28 hours
GC tuning experiments specifically: ~5 hours
JIT-related investigation: ~4 hours
Charts, tables, results wrangling: ~4 hours
Blog post (this one is difficult β youβre translating hard technical content): ~15 hours
README, PROFILING.md, LinkedIn post: ~4 hours
Total: ~64 hours over 4-5 weeks (~14-16 hours/week during M9)
M9 is a heavy month. This rung deserves the full month.
PrerequisitesΒΆ
Rung 5 shipped (this rung profiles Rung 5βs code)
All files in
06_jvm_internals_performance/read and worked throughAsync-profiler installed and working on your machine (10-30 min setup)
JMH already in your workflow from Rung 5
Read: βJava Performanceβ by Scott Oaks (2nd ed.) β chapters 5-8 minimum
Familiarity with G1 and ZGC log outputs (know what
Concurrent Markmeans, know whatPause Fullmeans)Access to a stable benchmarking machine (dedicated laptop, home server, or EC2 c-series instance)
Stretch Goals (Optional)ΒΆ
Add a JFR + Mission Control section. Async-profiler is the primary tool; JFR is the JDK-native alternative. Showing both signals fluency across the toolchain.
Publish a follow-up post: βHow to read a Java flame graph in 5 minutes.β This is an educational companion piece. Educational posts pull heavier long-tail traffic than case studies. Two posts, compounding SEO.
Contribute a real-world perf fix to an OSS project. Once you have the profiling instinct, look at a mid-size Java OSS repo (Micronaut, jOOQ, Jackson) and find a documented performance issue. Even one merged PR is a career-marker.
Return to README.md Β· Next: 07_rung7_url_shortener_service.md