JDK 25 makes ZGC generational-only and promotes Generational Shenandoah to product status. Use this tuning cheat sheet to choose and tune fast. Read now.

What Changed in JDK 25

JDK 25 narrows the collector menu in a useful way. ZGC is generational-only, so you no longer choose between a single-generation and a generational mode—you get the design that keeps short-lived objects out of the expensive concurrent paths. Generational Shenandoah moves to product status, which means it is no longer an experimental side path: it is a first-class option you can enable and operate with the same confidence you give other production collectors.

That shift matters for tuning. Both collectors now assume most allocations die young. Your job is less about enabling a mode and more about matching heap shape, allocation rate, and latency goals to a generational concurrent design.

Choosing Between Generational ZGC and Generational Shenandoah

Start from the workload, not from brand preference. Prefer the collector whose failure modes match how you run the service. If you need very low pause goals under steady allocation and can afford the concurrent work those pauses hide, either generational collector is in scope. If you already standardized on one stack’s defaults, tooling, or operator runbooks, stick with that collector unless you have a measured reason to switch.

Use Shenandoah when you want a concurrent compacting collector that has long emphasized short pause times with a relatively straightforward operational story. Use ZGC when you want a concurrent collector designed around very large heaps and tight pause targets, now always in its generational form. If both look viable, pick one, baseline it, and only swap after a controlled A/B under production-like load—not after a single synthetic microbenchmark.

Tuning Cheat Sheet

Keep the baseline simple. Size the heap so the young generation can absorb normal allocation bursts without thrashing into concurrent cycles. Leave enough headroom that concurrent marking and relocation can finish before free space is exhausted. Watch allocation rate, heap occupancy after concurrent cycles, and promotion into the old generation; those signals tell you whether the collector is keeping up, not whether a single pause number looked pretty in a demo.

  • Set an explicit max heap and stick to it across environments so results are comparable.
  • Raise heap size before you chase exotic flags when concurrent cycles cannot complete in time.
  • Lower live-set pressure (object churn, caches without bounds, huge retained graphs) before blaming the collector.
  • Align GC logging and continuous profiling so you can see cycle frequency, not only pause samples.
  • Change one control at a time and keep a rollback path to the previous collector or heap size.

Avoid stacking many ergonomic overrides. Generational concurrent collectors already adapt; extra flags often paper over a heap that is too small or an application that retains more than it should.

A Practical Selection Path

Default path: enable one generational product collector, fix heap size and headroom, then validate under realistic traffic. If pause and throughput goals are met, stop tuning. If concurrent cycles fall behind, grow the heap or cut retained data before switching collectors. If both collectors meet goals at similar cost, choose the one your team can operate—monitoring, runbooks, and incident muscle matter more than a small theoretical edge.

JDK 25’s message is operational clarity: ZGC is generational-only, Generational Shenandoah is product-ready, and the winning move is a short, disciplined tune loop rather than a long flag hunt.

Automate Your Content with AI Video Generator

Try it Free →