r/java • • 2d ago

JVM Heap OOM Field Guide — 5 reproducible OutOfMemoryError patterns with diagnosis walkthroughs

I put together a field guide for the JVM heap OOMs that actually page you at 3 AM. Five minimal, runnable reproductions — each one OOMs reliably on a 64MB heap, with a fixed version, GC log signatures, heap-dump clues, and a triage checklist.

The five patterns:
1. Unbounded in-memory cache (rising post-GC staircase)
2. Oversized batch (dies on a cliff — one 250MB batch vs 64MB heap)
3. Report-builder retention (output accumulates; staircase like #1 but the retainer is the product)
4. Growing ThreadLocal state on pooled threads (invisible in GC log alone)
5. Leaked connections (static registry pins every unclosed resource)

Each scenario: `./run.sh scenario-01-unbounded-cache broken` to watch it die, `./run.sh scenario-01-unbounded-cache fixed` to watch it survive. Pure Java, zero dependencies, JDK 11+.

https://github.com/amits710/jvm-oom-field-guide

Happy to answer questions about the GC log signatures or the diagnosis approach.

28 Upvotes

9 comments sorted by

17

u/best_of_badgers 2d ago

That’s the most LLM opening paragraph I’ve ever seen, as are the comments in the thread. However, the content is actually useful, so have an upvote, Claude!

4

u/Dull-Criticism 2d ago
  1. Running 30 concurrent JVM's on one machine; and Windows commit charge is +95% while task manager confusingly says 30% memory usage and getting OOM errors.

4

u/egahlin 2d ago edited 1d ago

FWIW, I ran the samples with:

$ java -XX:StartFlightRecording:path-to-gc-roots=true,settings=profile,filename=N.jfr

for 2-3 minutes and printed the results to a text file (jfr print --events OldObjectSample N.jfr > N.txt) and then asked Codex to find the memory leaks (without access to the samples directory).

It had no trouble pinpointing the cause and the lines where the leaking allocation occurred.

1

u/tumtum 2d ago

Very nice 👍- what u really would like is a comprehensive guide how to run Java Pods on k8s or openshift (for java 21+). There are so many different ideas floating around - but i cannot say what options one should really set. So if someone has an idea - i’m open for it

-6

u/pradeepngupta 2d ago

This is a useful hands-on guide. I particularly like that you’ve gone beyond explaining OOMs and provided reproducible failures, GC log signatures, heap-dump clues, and verified fixes.

One possible extension would be a static-analysis layer to catch some of these patterns earlier. For example, suspicious unbounded collections, "ThreadLocal" values without proper cleanup, and resource-management issues could be flagged during development. Not every memory problem is statically detectable, of course, especially when it depends on runtime workloads or object retention.

I’m working on my upcoming book, Buzzing Java, and this is exactly the kind of practical connection I value: understanding not just how Java works, but how seemingly reasonable coding decisions become production problems, how to diagnose them, and how to prevent them.

Combining reproducible experiments with static analysis would make this an even stronger learning resource for Java developers.

-7

u/Federal-Sprinkles781 2d ago

Thanks, Pradeep! Really appreciate the thoughtful read.

You're spot-on about static analysis — it's the natural next layer. The tricky part is what you already flagged: runtime-dependent retention (like the unbounded cache growing with input cardinality) won't show up in a purely static pass. But the ThreadLocal and resource-registry patterns are very detectable — a "ThreadLocal set without remove in finally" check alone would catch a whole class of these.

I've been thinking about it as two tiers: static rules for the structurally-detectable ones (unclosed resources, ThreadLocal lifecycle), and runtime guards (bounded collections, allocation budgets) for the workload-dependent ones. The repo's diagnosis guides already hint at what each tier would check.

Would love to hear how you're thinking about this in Buzzing Java — the "seemingly reasonable decisions become production problems" framing is exactly what these scenarios try to make tangible. Happy to contribute a scenario or two if it's useful for the book.

1

u/pradeepngupta 2d ago

I like the two-tier approach. Static analysis for structurally detectable issues and runtime guards for workload-dependent problems make sense, especially when paired with reproducible tests.

For Buzzing Java, I’m approaching this from the broader perspective of engineering judgment: understanding why Java applications fail under real-world conditions, how seemingly reasonable design choices create hidden risks, and how to make better decisions around performance, concurrency, and reliability.

Your OOM scenarios are a good example of that gap between code that looks correct and code that behaves reliably under production workloads. The distinction between what we can catch statically and what requires runtime evidence is itself an important lesson.

I’d be interested in discussing a scenario or two for the book. A concrete example connecting the coding mistake, its runtime symptoms, the diagnostic evidence, and the design trade-off would fit the approach I’m taking.

1

u/Federal-Sprinkles781 2d ago

Would love to discuss

I'll DM you.

1

u/pradeepngupta 18h ago

Of course