zerve.gg
Back to blog

Best JVM Flags for Minecraft Servers in 2026: G1GC vs ZGC vs Shenandoah

Your garbage collector choice is the single biggest JVM decision you’ll make for a Minecraft server. Here’s why.

Minecraft’s server thread runs a game loop at 20 ticks per second. Each tick has a 50ms budget. When the JVM triggers a stop-the-world GC pause, the server thread freezes. A 200ms pause means 4 missed ticks. Players see entities rubber-band, blocks snap back, and redstone circuits skip beats. A 400ms pause (8 ticks) is plainly visible to everyone online.

The right collector depends on three things: how much heap you’re allocating, which Java version you’re running, and how many CPU cores you have. This guide covers the three collectors that matter in 2026, explains when each one wins, and gives you copy-paste flag sets. If you haven’t confirmed GC is actually your bottleneck yet, check /spark gc first; our guide to reading a Spark profiler report covers how to tell GC pressure apart from other lag sources.

G1GC: The Proven Default

G1GC (Garbage-First Garbage Collector) has been the JVM default since Java 9, replacing the throughput-oriented Parallel GC. It divides the heap into equally-sized regions and categorizes them as eden, survivor, or old generation. During a young collection, G1 evacuates live objects from eden and survivor regions into new regions, reclaiming the originals instantly.

The key tuning knob is -XX:MaxGCPauseMillis (default: 200ms). G1 uses this as a soft target, adjusting how many regions it collects per cycle to stay near that goal. It does not guarantee the target, but it actively works toward it.

The Humongous Object Problem

Any object larger than half a region’s size is classified as “humongous” and allocated directly into old generation regions. In Minecraft, chunk loading and world generation can produce these large allocations. Humongous objects skip the young generation entirely and only get collected during expensive old-gen cycles. Java 21 improved this by allowing G1 to move humongous objects during full GC and parallelize TLAB setup, reducing full GC pause times.

Aikar’s Flags: G1GC Tuned for Minecraft

Aikar’s flags are the community standard for G1GC tuning on Minecraft servers. They exist because Minecraft’s allocation pattern is unusual: the server allocates heavily every tick (entity updates, chunk processing, packet serialization), and most of those objects die within one GC cycle.

The critical flags and what they do:

For a deeper walkthrough of each flag and the reasoning behind it, see our Aikar’s Flags Explained post.

When to Use G1GC

G1GC is the right choice for most Minecraft servers. It works on any hardware, any heap size from 2 GB to 16 GB, and delivers predictable results. With Aikar’s flags applied, expect average pause times in the 20-40ms range and p99 pauses under 200ms on a properly sized heap.

G1GC improved dramatically from Java 8 through 17, and saw further incremental gains in Java 21. If you’re still on Java 8 or 11, upgrading to 21 with Aikar’s flags is likely the single largest performance improvement available to you.

Best for: 4-12 GB heap, any hardware, any experience level.

ZGC: Sub-Millisecond Pauses

ZGC takes a fundamentally different approach. Instead of stopping the application to move objects, ZGC performs nearly all of its work concurrently with the application threads. It uses colored pointers (metadata stored in unused bits of 64-bit object references) and load barriers (small code snippets injected at every object reference load) to track and relocate objects while the server keeps running.

The result: GC pauses measured in microseconds, not milliseconds. And those pauses stay constant regardless of heap size. A 4 GB heap and a 128 GB heap produce the same pause times.

Generational ZGC: The Critical Distinction

If you’re evaluating ZGC in 2026, you need generational mode. Here’s the history:

On Java 21 or 22, you must explicitly enable it: -XX:+UseZGC -XX:+ZGenerational. On Java 23+, -XX:+UseZGC alone gives you generational mode.

For more on the design, see the Inside Java Generational ZGC explainer and the OpenJDK ZGC project page.

The Trade-offs

ZGC is not free. You’re trading memory and CPU for pause-time elimination:

Benchmark: G1GC vs Generational ZGC

The MineGuard benchmark tested G1GC against Generational ZGC under controlled conditions:

Test environment: Paper 1.21.4, Java 21.0.5 (Temurin), AMD Ryzen 9 5950X, 64 GB DDR4, 200+ players online with active mob farms and Chunky chunk pre-generation, 3-hour sustained load per collector.

MetricG1GC (8 GB heap)Gen ZGC (16 GB heap)
Average GC pause28ms0.18ms
P99 GC pause145ms0.9ms
Max GC pause312ms2.1ms
Lowest TPS18.419.6

Note the heap difference: ZGC needed 16 GB to match what G1 did in 8 GB, confirming the memory overhead. But the pause-time improvement is dramatic. G1’s 312ms worst case is 6 missed ticks. ZGC’s 2.1ms worst case is imperceptible.

Important: Do Not Mix Aikar’s Flags with ZGC

Aikar’s flags are G1GC-specific. Flags like G1NewSizePercent, G1MaxNewSizePercent, and InitiatingHeapOccupancyPercent are G1 internals that ZGC ignores or, worse, that cause confusing warning messages. If you switch to ZGC, start with a clean flag set.

Best for: 12+ GB heap, 4+ real CPU cores, Java 21+.

Shenandoah: The Cautionary Tale

Shenandoah, developed by Red Hat, is another concurrent low-pause collector. It uses Brooks forwarding pointers (an extra word before each object that points to the object’s current location) to enable concurrent compaction. This means Shenandoah can move objects while the application is running, similar to ZGC but with a different mechanism.

Red Hat’s own benchmarks on Java 17 show initial-mark pauses as low as 63 microseconds. On paper, that’s excellent.

Why It Doesn’t Work for Minecraft Servers

There are three problems:

1. Non-generational design. Shenandoah does not separate young and old objects. Every collection cycle must consider the entire heap. For Minecraft, which generates enormous volumes of short-lived objects every tick, this is deeply inefficient. A generational Shenandoah mode was developed but has not landed in mainline OpenJDK.

brucethemoose’s benchmarks tested Shenandoah against G1GC and ZGC in Minecraft-specific workloads. The conclusion: “Shenandoah performs well on clients, but kills server throughput in my tests.” Without a young generation to cheaply collect tick-scoped allocations, Shenandoah’s concurrent threads burn excessive CPU scanning the full heap.

2. Availability. Shenandoah is not included in Oracle JDK builds. You need an OpenJDK distribution that includes it, such as Eclipse Temurin or Amazon Corretto. This limits deployment flexibility and can create confusion when switching between JDK vendors.

3. Community verdict. The PaperMC Velocity tuning guide states that the Velocity team “is not aware of any successful deployments of Shenandoah with Velocity in the wild.” When the largest Paper-family project effectively warns against it, that’s a strong signal.

Best for: Client-side JVMs and non-Minecraft workloads. Not recommended for Minecraft servers.

Parallel and Serial GC: Don’t

Two other collectors ship with the JVM. Neither belongs on a Minecraft server.

Parallel GC (formerly the default before Java 9) is a throughput-maximizing collector. It stops the entire application for every collection, using multiple threads to finish quickly. “Quickly” is relative: pauses can reach multiple seconds on large heaps. The PaperMC Velocity documentation warns that Parallel GC’s “pause times tend to be long, and are not suitable for Minecraft.”

Serial GC is a single-threaded stop-the-world collector designed for embedded systems and JVMs with tiny heaps (under 100 MB). If you see -XX:+UseSerialGC in your startup script, something has gone wrong.

Java Version Matters

Your Java version determines which collectors are available and how well they perform.

Minecraft VersionMinimum JavaNotes
1.17Java 16Java 21 works and is recommended
1.18 - 1.20.4Java 17Java 21 works and is recommended
1.20.5 - 1.21.xJava 21Required, not optional
1.22+Java 21Check server requirements for updates

Key version milestones for GC:

Our recommendation for 2026: Java 21 LTS as the baseline. It’s required by modern Minecraft versions, has the best GC options, and will receive security updates through at least 2029.

Decision Matrix

Heap SizeJava VersionHardwareRecommended GC
Under 4 GBAnyAnyG1GC with Aikar’s flags (but consider if the server needs more RAM)
4 - 12 GB21AnyG1GC with Aikar’s flags
12 - 16 GB214+ cores, 16+ GB system RAMBenchmark both G1GC and Generational ZGC
16+ GB216+ coresGenerational ZGC
Any23+AnyGenerational ZGC (default)

The 12-16 GB range is genuinely ambiguous. G1GC with Aikar’s flags still performs well here, and uses less total memory. ZGC eliminates tail-latency spikes but needs the extra headroom. If your server regularly hits 100+ players or runs heavy modpacks (ATM9, RLCraft), the ZGC trade-off is usually worth it. For vanilla or lightly modded servers, G1GC is fine.

Quick-Start Commands

G1GC with Aikar’s Flags (10 GB)

java -Xms10G -Xmx10G \
  -XX:+UseG1GC \
  -XX:+ParallelRefProcEnabled \
  -XX:MaxGCPauseMillis=200 \
  -XX:+UnlockExperimentalVMOptions \
  -XX:+DisableExplicitGC \
  -XX:+AlwaysPreTouch \
  -XX:G1NewSizePercent=30 \
  -XX:G1MaxNewSizePercent=40 \
  -XX:G1HeapRegionSize=8M \
  -XX:G1ReservePercent=20 \
  -XX:G1MixedGCCountTarget=4 \
  -XX:InitiatingHeapOccupancyPercent=15 \
  -XX:G1MixedGCLiveThresholdPercent=90 \
  -XX:G1RSetUpdatingPauseTimePercent=5 \
  -XX:SurvivorRatio=32 \
  -XX:+PerfDisableSharedMem \
  -XX:MaxTenuringThreshold=1 \
  -jar server.jar --nogui

Generational ZGC (16 GB)

java -Xms16G -Xmx16G \
  -XX:+UseZGC \
  -XX:+ZGenerational \
  -XX:+AlwaysPreTouch \
  -XX:+DisableExplicitGC \
  -XX:+PerfDisableSharedMem \
  -jar server.jar --nogui

ZGC needs far fewer flags because it self-tunes aggressively. The flags above are sufficient for most deployments. Do not add G1-specific flags.

A Note on Xms = Xmx

Both examples set -Xms equal to -Xmx. This is intentional. When Xms is lower than Xmx, the JVM starts with a small heap and grows it on demand. Each growth event triggers a GC pause. On a Minecraft server, that pause freezes every player. Pre-allocating the full heap at startup eliminates resize pauses entirely. Our analysis of 7,661 real server configs found that 15% of servers still make this mistake.

How We Handle This at Zerve

Every Zerve server ships with the optimal GC configuration for its plan size. Servers on 4-12 GB plans get G1GC with Aikar’s flags. Larger plans get Generational ZGC with tuned concurrent thread counts matched to their dedicated CPU cores. You don’t need to paste flags into a startup script or guess which collector fits your workload.

Our Spark Analyzer can also diagnose GC issues on any server, not just ours. Paste a Spark profile link and it will flag misconfigured collectors, missing Aikar’s flags, and Xms/Xmx mismatches automatically.

Sources