For many OpenJ9 users, garbage collection tuning starts with the wrong question.
They ask, “Which collector is the fastest?” or “Which one is best for cloud?” In practice, the better question is: what does my application actually need from the heap?
That is the right starting point because OpenJ9 gives you several distinct GC policies, each designed around a different balance of throughput, pause time, heap geometry, and operational behavior. The default is gencon, and for a broad spectrum of Java applications that is still the right place to start. But once heaps grow, pause spikes become operationally expensive, or throughput becomes the dominant goal, a different policy may be the better fit.
Modern Java deployments make this more relevant, not less. Kubernetes microservices, virtual-thread-heavy services, streaming systems, API gateways, JVM-based AI orchestration layers, RAG services, batch analytics, and memory-dense backend platforms do not all behave in the same way. They should not all inherit the same GC assumptions by default.
This guide is not meant to turn every reader into a GC specialist. Its goal is simpler: help OpenJ9 users choose a sensible starting policy, understand the tradeoffs behind each option, and know when a different choice is justified. For the official baseline, see the OpenJ9 GC policies page and the OpenJ9 -Xgcpolicy reference.
How to choose an OpenJ9 GC policy
The most useful way to think about GC policy selection is to begin with workload shape.
Are you allocating many short-lived objects? Is the heap large enough that pause consistency matters more than raw throughput? Is the workload interactive and latency-sensitive, or throughput-first and non-interactive? Do you need deterministic pauses, or just a better pause profile than you have today? Is the process so short-lived that reclaiming memory is mostly wasted work?
Those are the real decision points.
OpenJ9’s own documentation aligns well with that view. The default gencon policy is described as a strong general-purpose choice, especially for transactional applications with many short-lived objects. The same documentation also emphasizes that policy choice should be informed by real GC logs rather than assumptions. In other words, selection is part of a measurement loop, not just a startup flag. See the OpenJ9 GC policies page and the verbose GC logging guide.
Before diving into the policies, one note on comparisons: the HotSpot mappings below are mental models, not equivalence claims. They are there to help experienced HotSpot users quickly orient themselves, not to imply identical ergonomics or pause behavior.
OpenJ9 GC policy comparison table
If you want the short answer first, start here:
| Policy | Best starting fit | Main strengths | Main tradeoffs | Modern workload examples |
gencon | General-purpose server workloads with many short-lived objects | Strong default balance of throughput and pause time; broad-spectrum fit for many transactional workloads | Can run into fragmentation, global compaction pressure, or variability issues on some large or uneven heaps | Spring Boot APIs, REST services, event-driven services, Kafka consumers/producers, virtual-thread services, LLM/RAG gateways, agent orchestration backends |
balanced | Large heaps, often in the tens of GB, where gencon shows compaction-driven pause spikes or rising pause variability | Better pause profile on large heaps; addresses several large-heap pain points more gracefully than gencon | More specialized than gencon; no hard pause bound; 64-bit only; can give up some throughput | Large monoliths, cache-heavy backends, JVM analytics coordinators, search/routing platforms, memory-dense stateful services |
optavgpause | Large flat heaps where lower average pause matters more than throughput | Lower average pause than optthruput; can avoid some generational-specific overheads | Reduced throughput; still not a real-time policy; usually needs more heap than generational policies | Some session-heavy services, selected large-heap backends, specialized long-running services |
optthruput | Throughput-first, non-interactive workloads where completion rate matters more than pause time | Good fit when total work matters more than pause consistency; useful flat-heap baseline | Long stop-the-world pauses can occur; typically wants more heap than generational policies | Batch ETL, offline analytics, reconciliation jobs, blockchain indexing pipelines, data preprocessing |
metronome | Deterministic pause-sensitive systems with real pause bounds in the requirements | Short, bounded pauses with explicit utilization goals | Specialized tuning, fully expanded heap, limited platform support, noticeable throughput cost | Real-time control, telecom, industrial or embedded Java services |
| nogc | Very short-lived or tightly controlled runs where you want to remove GC effects for experimentation or baselining | Minimal GC runtime impact; useful for controlled experiments, performance runs, and GC-free baselines | No reclamation at all; heap exhaustion stops the VM; very few real-world long-running applications are a good fit | Short-lived tools, controlled benchmarks, tightly bounded helper processes |
Concurrent Scavenge (gencon mode) | Large-heap, response-time-sensitive gencon workloads where lower average pause time matters | Can reduce average nursery pause time without leaving the generational model | Not a separate policy; worst-case pauses can still be significant; tuning may still be needed | Large-heap transactional services, response-time-sensitive server workloads already using gencon |
The official descriptions for the six primary policies are in the OpenJ9 GC policies page and the OpenJ9 -Xgcpolicy reference. Concurrent Scavenge is documented separately under OpenJ9 -Xgc options, because it is a gencon mode rather than a separate policy.
gencon
In one line: gencon is the right starting point for most transactional, server-side Java workloads.
Most closely resembles: Hotspot CMS as a mental model on Java 8, and in some usage patterns the young-generation behavior people often associate with default server collectors. This is only an orientation aid, not a one-to-one mapping.
How it works?
gencon is OpenJ9’s default generational concurrent policy. The heap is split into a nursery and tenure space. Young objects are handled with Scavenge collections in the nursery, while old-generation work is handled through concurrent global mark with optional compaction. In practice, this design is optimized for workloads with high short-lived object churn. It also uses heuristics around tenuring thresholds, Tenure headroom, and nursery tilting to balance allocation pressure and liveness. Those heuristics are powerful, but under highly variable workloads they can also mispredict and contribute to aborted Scavenges, which are expensive to recover from.
Strengths
For mainstream server-side workloads, gencon often delivers the strongest overall throughput profile while still keeping average pause times and, in many cases, worst-case pauses within acceptable ranges. It has excellent footprint characteristics, aligns well with short-lived allocation churn, and requires the least justification to adopt because it is already the OpenJ9 default. It is broad-spectrum and remains the best baseline for many cloud-native services, messaging backends, and AI orchestration layers.
Weaknesses & Mechanical Limits
This is where the deep mechanics matter. If object sizes and lifetimes vary widely, fragmentation can build up and lead to global heap compaction. If class unloading becomes expensive, global GC can become more disruptive than average pause metrics suggest, especially in workloads that churn many class loaders. The simple nursery/tenure geometry can also become harder to balance when the heap reaches full expansion. In that state, the VM may struggle to decide whether the nursery should continue to grow at the cost of tenure, or vice versa.
Workloads using JNI Critical with large arrays can trigger extra global activity because pinned objects cannot be moved by Scavenge. Highly variable workloads can also expose weaknesses in pre-emptive concurrent global kickoff, Tenure headroom, and nursery tilting heuristics. In extreme cases that can lead to aborted Scavenges, which produce exactly the kind of long pauses teams are trying to avoid. Tuning can reduce the problem, but only up to a point.
Practical Recommendation
Start with gencon for most general-purpose Java services: Spring Boot and Jakarta EE applications, REST and GraphQL APIs, event-driven microservices, Kafka-connected services, virtual-thread-based request handlers, and most JVM-based AI orchestration layers. If the workload remains request-driven and dominated by short-lived allocation churn, the default is usually right. Move away from gencon only when the workload gives you a concrete reason, such as compaction-driven pause spikes, class-unloading costs, or large-heap variability that tuning does not adequately address. See the OpenJ9 GC policies page and IBM’s garbage collection tradeoffs and tuning with OpenJ9 article.
balanced
In one line: balanced is the first collector to test when larger heaps make pause behavior harder to control under gencon.
Most closely resembles: HotSpot G1GC as a mental model, especially for readers who think in terms of region-based heap management. The OpenJ9 new-user material also positions it as the closest OpenJ9 policy to the default HotSpot policy on Java 11.
How it works?
balanced is a 64-bit-only policy that divides the heap into regions managed by an incremental generational collector. Its purpose is to reduce maximum pause time on large heaps by changing the geometry of the problem. Instead of relying on a simple nursery/tenure split, it can perform more incremental work and react more flexibly to large-heap behavior. It can also transition Partial GC behavior dynamically, for example from evacuating style work to mark-and-compact when empty space is tight. That is one reason it often handles variability more gracefully than gencon under memory-dense conditions.
Strengths
The strength of balanced is not simply that it is “better for large heaps”. Its real value is that it reduces pressure from some of the most expensive whole-heap activities. Defragmentation becomes more incremental. Class unloading can happen with finer granularity. JNI Critical pinning tends to be less disruptive. Eden size tuning pressure is reduced because region-based geometry and balancing heuristics can adapt more smoothly than a strict nursery/tenure split. In some large, memory-dense systems, balanced can feel more plug-and-play than gencon, even if it is less commonly used.
Weaknesses & Mechanical Limits
The main tradeoff is that balanced buys flexibility at some cost in application throughput and average GC cost. It is not a real-time collector and does not provide a hard pause bound. Actual pause behavior still depends on allocation rates, survival rates, and fragmentation. In other words, it improves the pause profile for the kinds of large-heap problems it is designed for, but it does not turn a large heap into a deterministic one. It is also 64-bit only.
What counts as a large heap? The best answer is behavioral rather than purely numeric. In practice, many teams start looking harder at balanced once heaps move into the tens of gigabytes, often around 16 GB to 32 GB and above, but there is no universal cutoff. The more meaningful trigger is a combination of heap size, live-set shape, allocation behavior, and symptoms in GC logs, especially global collections, compaction-driven pause spikes, or rising pause variability under gencon.
Practical Recommendation
Evaluate balanced when gencon is fundamentally workable but pause behavior becomes unacceptable as the heap grows. It is a strong candidate for large memory-heavy services, cache-rich monoliths, search or analytics coordinators, and AI-serving platforms that hold large caches, retrieval metadata, embeddings-related state, or other long-lived coordination structures. The right message is not “use balanced for low latency”. The right message is “use balanced when large heaps are making pause behavior less predictable under gencon, and you need a smoother pause profile without moving into deterministic GC territory”. See the OpenJ9 GC policies page and IBM’s OpenJ9 tuning article.
optavgpause
In one line: optavgpause is a specialized flat-heap option when lower average pause matters more than throughput.
Most closely resembles: There is no perfect HotSpot equivalent. Conceptually it sits in the family of pause-oriented non-generational collectors, but it is better understood on its own terms as a flat-heap tradeoff policy.
How it works?
optavgpause is a flat-heap policy that starts global GC preemptively so the cycle can complete before the heap is exhausted. It does not use a nursery/tenure generational split. That means some of the generational-specific mechanisms and pathologies simply do not exist in the same form. There is no nursery tilting, no classic aborted Scavenge, and no remembered-set maintenance cost in the same sense as a generational collector. The tradeoff is that more work must be done across the flat heap, which shifts cost from worst-case pause behavior toward average throughput.
Strengths
Its core strength is exactly what the name suggests: lower average pause time than optthruput on workloads where that tradeoff matters. OpenJ9 also notes that its non-generational write-barrier strategy can help some workloads that frequently modify large, old reference arrays. It can therefore be attractive when you intentionally want a flat-heap policy and you are trying to avoid some of the generational-specific overheads that show up in gencon.
Weaknesses & Mechanical Limits
The main tradeoff is reduced throughput. Like any non-generational policy, optavgpause usually wants more heap than a generational collector to keep overhead in the same general range. And OpenJ9 is explicit that in many situations the default gencon policy still performs better overall. That is an important limitation to state clearly because it prevents optavgpause from sounding like a universally safer or more modern answer when it is really a specialized one.
Practical Recommendation
Consider optavgpause when you have a large heap, you are intentionally evaluating flat-heap policies, average pause improvement matters, and gencon and balanced are not giving the behavior you need. It is more plausible for selected long-running services or session-heavy workloads than for mainstream request-driven cloud applications.
optthruput
In one line: optthruput is the right collector to evaluate when completion rate matters more than pause consistency.
Most closely resembles: Conceptually closest to a throughput-first flat-heap strategy, and as a rough HotSpot mental model it plays a role similar to how engineers think about ParallelGC style tradeoffs: throughput first, pause time second. That is a directional analogy, not an equivalence claim.
How it works?
optthruput is a flat-heap policy that runs global mark-sweep, optionally followed by compaction, when allocation failure occurs. Because the collector requires exclusive access to the heap, application threads halt while GC runs. This is the most direct, throughput-first shape in the OpenJ9 policy family. It also provides a useful baseline when you want to understand what more advanced policies are costing you in mechanisms such as write barriers, remembered sets, or extra concurrent work.
Strengths
In the right workload, optthruput can deliver strong overall completion efficiency. It also avoids some issues with very large objects that can appear with gencon‘s nursery/tenure structure. For teams doing experimentation, optthruput and optavgpause can also be useful comparison points for understanding the cost of more advanced policy machinery.
Weaknesses & Mechanical Limits
The downside is exactly as serious as it sounds: long stop-the-world pauses. That alone rules optthruput out for many user-facing services. Like other non-generational policies, it typically wants more heap than a generational collector to maintain comparable overhead. It is also worth stating plainly that “throughput-first” does not automatically mean “higher throughput than gencon“. In many real server workloads, gencon still wins overall. The value of optthruput is that it can be the right answer for the right class of workloads, not that it supersedes the default.
Practical Recommendation
Use optthruput when throughput is clearly the primary objective and latency spikes are acceptable. It fits naturally with non-interactive, throughput-first jobs: batch ETL, offline analytics, reconciliation work, scheduled reporting, blockchain indexing, ledger analysis, and large data preprocessing pipelines for AI systems. See the OpenJ9 GC policies page.
metronome
In one line: metronome is for systems that genuinely need bounded GC pauses.
Most closely resembles: Conceptually closer to the family of deterministic low-pause collectors, and as a rough mental model readers may compare its goals to what they expect from ZGC/Shenandoah-style pause-focused designs. But metronome is really its own thing: an incremental deterministic collector with explicit utilization goals.
How it works?
OpenJ9 documents metronome as being designed for applications that require a precise upper bound on collection pause times together with a specified application utilization target. It runs in short interruptible bursts to avoid long stop-the-world pauses. It is supported only on Linux x86-64 and AIX, and it uses a fully expanded heap even when -Xms is lower than -Xmx. One mechanical consequence is that certain JNI array access paths can become slower because of arraylet reconstitution.
Strengths
Its strength is not convenience. Its strength is determinism. If your system genuinely requires bounded GC pauses, metronome is the OpenJ9 policy built for that purpose. That is the key reason it exists.
Weaknesses & Mechanical Limits
The costs are real: limited platform support, iterative heap and utilization tuning, a fully expanded heap, slower JNI array access in some scenarios because of arraylet mechanics, and noticeably compromised throughput compared with more general-purpose policies. This is not a collector to adopt casually, and it should not be described as a generic low-latency upgrade for mainstream cloud services.
Practical Recommendation
Use metronome for systems where deterministic pause behavior is genuinely part of the requirements: selected telecom, industrial, control, or embedded-style Java systems. See the OpenJ9 GC policies page.
nogc
In one line: nogc is a useful specialist tool for baselining and controlled runs, but very few real long-running applications should use it.
Most closely resembles: There is no real HotSpot equivalent because the defining characteristic here is the absence of reclamation, not a different reclamation strategy.
How it works?
OpenJ9 says nogc handles allocation and heap expansion, but does not reclaim memory. If the heap is exhausted, an OutOfMemoryError is triggered and the VM stops. Mechanically, that makes it the cleanest way to remove GC reclamation effects from a run, but it also makes it unsuitable for most real systems.
Strengths
Its value is in controlled situations: benchmarking baselines, validating memory sizing, very short-lived programs, tightly bounded helper processes, and experiments where you want to remove the effect of GC from the measurement as much as possible.
Weaknesses & Mechanical Limits
The weakness is absolute: memory is never reclaimed. That makes nogc risky for almost all long-running services. OpenJ9 also explicitly warns to be careful with finalization, direct memory access, and weak, soft, and phantom references under this policy. And to make the point explicit: very few real-world long-running applications are a good fit for nogc.
Practical Recommendation
Treat nogc as a specialist tool, not a production default. Use it for controlled experiments, bounded helper processes, and GC-free baselines. See the OpenJ9 GC policies page.
Concurrent Scavenge (gencon mode)
In one line: Concurrent Scavenge is an advanced gencon mode that can improve average pause behavior for some large-heap, response-time-sensitive workloads.
Most closely resembles: Conceptually overlaps with how readers think about ZGC or Shenandoah in the sense that it tries to improve average pause behavior with more concurrent work, but it remains a gencon mode and does not provide the same style of worst-case guarantees.
How it works?
It is not a separate GC policy. It is a gencon mode enabled through –Xgc:concurrentScavenge. OpenJ9 documents it as a pause-less garbage collection mode for the Generational Concurrent policy, intended to reduce GC pause times for response-time-sensitive, large-heap applications. It is 64-bit only and cannot be used with the other policies.
Mechanically, it is useful to think of Concurrent Scavenge as replacing a traditional stop-the-world Scavenge with shorter STW opening and closing phases and a Concurrent Copy phase in between. That means average pause time can improve significantly, while preserving the benefits of a generational model.
Strengths
It sits in a very useful middle ground between “stay on plain gencon” and “switch policies entirely”. For some large-heap, response-time-sensitive services, it can reduce average nursery pause time without abandoning the generational model. Concurrent Scavenge preserves relatively strong throughput and footprint characteristics compared with non-generational alternatives.
Weaknesses & Mechanical Limits
It is still a tuning option, not a universal answer. Worst-case pauses can remain significant, especially under highly variable workloads or CPU over-utilization. Aborted Scavenges remain possible, which is why this is not a magical low-pause switch. It improves average pause behavior, but it does not erase every worst-case scenario.
Practical Recommendation
Evaluate Concurrent Scavenge before switching away from gencon if your workload is already fundamentally a good fit for a generational collector but average pause reduction matters, especially on large heaps in response-time-sensitive services. See OpenJ9 -Xgc options.
What to look for in OpenJ9 verbose GC logs
When checking the logs, it is important to know where to start.
OpenJ9’s verbose GC logs are one of the most useful official references because they support both tuning and troubleshooting. The logs capture GC operations, help analyze long pauses, show how free memory is divided in the heap before and after a cycle, and can be visualized with tools like GCMV. Logs are enabled with -verbose:gc, printed to STDERR by default, and can be written to files with -Xverbosegclog. See the verbose GC logs guide.
The annotated verbose GC log examples page is especially useful because it moves from raw XML to interpretation. The examples show how allocation-failure triggers appear, how scavenge cycles are marked, and how memory sections describe nursery and tenure state before and after collection.
In practical terms, GC logs help confirm policy mismatch:
- if you are on
genconand keep seeing long global-compaction-related pauses,balancedmay be worth testing - if allocation-failure-driven cycles dominate a non-interactive batch workload,
optthruputmay be more natural - if average pause behavior is the real problem on a large-heap
genconservice,Concurrent Scavengemay be worth evaluating before switching policies entirely
For a tuning-oriented companion overview, IBM’s garbage collection tradeoffs and tuning with OpenJ9 article is also useful.
A rough HotSpot mental model
For readers coming from HotSpot, a small amount of context helps.
OpenJ9’s new-user guide gives two valuable orientation points. On Java 8, gencon is the OpenJ9 policy most similar to HotSpot CMS. On Java 11, balanced is the OpenJ9 policy most similar to the default HotSpot policy. These are guideposts, not equivalence claims. OpenJ9 has its own ergonomics, heap geometry, and tradeoffs. The value of the comparison is simply to help readers anchor expectations more quickly.
A similarly useful mental model applies to Concurrent Scavenge: it occupies a conceptual space that overlaps with collectors like ZGC or Shenandoah in the sense that it tries to improve average pause behavior with more concurrent work. But again, this is an orientation aid, not an equivalence claim.
Workload-based policy guidance
If you want the shortest practical summary:
For cloud-native APIs, microservices, event-driven services, and most virtual-thread-heavy server applications, start with gencon.
For large memory-dense services where pause consistency has become the dominant pain point, evaluate balanced next.
For batch analytics, ETL, reconciliation, indexing, and offline preprocessing, consider optthruput, and in selected cases optavgpause.
For deterministic low-latency systems with real pause bounds in the requirements, evaluate metronome.
For very short-lived tools, memory baselining, or tightly controlled experiments, nogc can be useful, but only with clear heap boundaries and the understanding that memory will never be reclaimed. And for response-time-sensitive, large-heap gencon workloads where average pause reduction matters, Concurrent Scavenge is worth testing before switching policies completely.
FAQ
Which OpenJ9 GC policy should I use by default?
For most general-purpose Java server applications, start with gencon.
When should I switch from gencon to balanced?
When larger heaps start producing compaction-driven pause spikes, frequent global collections, or rising pause variability under gencon.
What counts as a large heap in OpenJ9?
There is no universal cutoff, but many teams start paying closer attention once heaps move into the tens of gigabytes, often around 16 GB to 32 GB and above. The better trigger is behavior, not a number.
Is Concurrent Scavenge a separate GC policy?
No. It is a gencon mode enabled through -Xgc:concurrentScavenge.
Is nogc suitable for production?
Usually no. It is mainly useful for controlled experiments, baselines, and very short-lived processes. Very few real-world long-running applications are a good fit.
Final thoughts
Most users do not need to overcomplicate OpenJ9 GC selection.
The best advice for the majority of modern Java applications is still straightforward: start with gencon, enable GC logging, establish a baseline from evidence, and switch policies only when your application behavior gives you a concrete reason. OpenJ9 offers specialized alternatives, but each one exists for a specific workload shape and tradeoff profile, not as a universal upgrade. See the OpenJ9 GC policies page for the authoritative policy descriptions.