JVM flags for Minecraft, demystified
JVM flags for Minecraft, demystified
You've seen the line. It looks like a small explosion:
-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions ...
This is "Aikar's flags," named after the Paper developer who popularized them, and they are pasted into approximately every Minecraft startup script on the internet. Most people who use them have no idea what they do. That's mostly fine. They are sensible defaults. But knowing what each chunk does makes you better at recognizing when to deviate.
The 30-second version
JVM flags are tuning knobs for Java's garbage collector and runtime. Minecraft is a "stop-the-world" type workload: when the JVM pauses for garbage collection, the game pauses too. Long pauses = lag. Short pauses = smooth.
Aikar's flags tune the G1 garbage collector to favor many short pauses instead of fewer long ones. For Minecraft, this trade is correct.
The flags, group by group
Picking the collector
-XX:+UseG1GC
Use the G1 (Garbage-First) collector. This is the right collector for Minecraft up through heap sizes of roughly 12 GB. Beyond that, ZGC starts winning on pause times. G1 is the default in modern Java anyway, so this is mostly explicit.
-XX:+UnlockExperimentalVMOptions
Allows the next group of flags to be set. They are not really "experimental" anymore but the JVM still classifies them that way.
Tuning G1 for Minecraft
-XX:MaxGCPauseMillis=200
Asks G1 to aim for pauses under 200 ms. It's a soft target. Reasonable for Minecraft, where a 200 ms pause is barely perceptible.
-XX:G1NewSizePercent=30
-XX:G1MaxNewSizePercent=40
Sets the young generation to 30 to 40 percent of the heap. The young generation is where new objects live until they survive a few GC cycles. Minecraft allocates many short-lived objects (block updates, packet objects), so a bigger young generation reduces how often things have to be promoted to the old generation.
How the heap is divided, with Aikar's flags on an 8 GB heap:
-XX:G1HeapRegionSize=8M
Divides the heap into 8 MB regions. Larger than the default. Helps when Minecraft allocates large objects (big chunks, big collections). Default heuristics often pick 4 MB or 2 MB, which is too small for our workload.
-XX:G1ReservePercent=20
Reserves 20 percent of the heap as headroom for promotion. Prevents the "to-space exhaustion" failure mode under high allocation pressure.
-XX:G1HeapWastePercent=5
How much waste G1 will tolerate before starting a mixed GC. Lower means more aggressive cleanup. Trades a tiny bit of throughput for steadier behavior.
-XX:G1MixedGCCountTarget=4
Try to complete mixed GC in 4 cycles. Spreads work out.
-XX:InitiatingHeapOccupancyPercent=15
Starts a concurrent GC cycle when 15 percent of the heap is used. Very aggressive. This is the most "Aikar-specific" choice. It tells G1 to start cleaning earlier than the default 45 percent, trading throughput for predictability. For Minecraft, this is right.
-XX:G1MixedGCLiveThresholdPercent=90
-XX:G1RSetUpdatingPauseTimePercent=5
Fine tuning for mixed GC behavior. Safe to leave alone.
Surviving allocation spikes
-XX:SurvivorRatio=32
-XX:+PerfDisableSharedMem
-XX:MaxTenuringThreshold=1
MaxTenuringThreshold=1 means objects get promoted to old generation after surviving one young GC. Default is 15. Aikar's setting is right for Minecraft because most surviving objects are genuinely long-lived (chunks, entities) and there's no point ping-ponging them in the young gen.
PerfDisableSharedMem disables a JVM feature that wrote performance counters to a shared memory file, which on some systems caused disk I/O hiccups. Mostly historical at this point but still safe to keep.
Heap size: the one knob most people get wrong
Heap size is set with -Xms (initial) and -Xmx (max). Standard advice: set them equal. For example:
-Xms6G -Xmx6G
Why equal? Because if min and max differ, the JVM will grow and shrink the heap, and the resizing operations are expensive. Set both to the same value.
How much to give? See the RAM-per-player article. Short version:
- Vanilla / Paper, small groups: 3 to 4 GB.
- Medium modded: 6 to 8 GB.
- Heavy modded: 10 to 12 GB.
Do not allocate all of your system's RAM to the heap. Leave headroom for the OS, the JVM itself (which uses memory outside the heap), and any other services on the box. An 8 GB host should not run an 8 GB heap. 6 GB is the right answer.
When to deviate from Aikar's flags
The flags assume:
- Heap between 4 and 12 GB.
- A "normal" Minecraft workload.
- The G1 collector is the right collector.
Your heap is larger than 12 GB. Switch to ZGC.
-XX:+UseZGC
-XX:+ZGenerational
ZGC has sub-millisecond pause times even at 30+ GB heaps. The tradeoff is slightly higher CPU and slightly higher memory overhead. For very large heaps it is unambiguously better than G1.
Your heap is under 2 GB. Don't bother with most of these flags. Use the JVM defaults. The flags are tuning for behavior G1 only really exhibits at larger heaps.
You're running Java 21+ on a recent CPU. Several G1 defaults have improved. You can probably remove InitiatingHeapOccupancyPercent and G1NewSizePercent and get similar results. Most people leave them in for safety.
A complete recommended startup
For an 8 GB Paper / Forge server on Java 21:
java -Xms8G -Xmx8G \
-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:G1HeapWastePercent=5 \
-XX:G1MixedGCCountTarget=4 -XX:InitiatingHeapOccupancyPercent=15 \
-XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 \
-XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 \
-Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true \
-jar server.jar nogui
For a heap above 12 GB, swap to:
java -Xms16G -Xmx16G \
-XX:+UseZGC -XX:+ZGenerational \
-XX:+AlwaysPreTouch \
-jar server.jar nogui
How to know if your flags are working
Monitor:
- Average TPS during normal play. Should sit at 20 with occasional brief dips.
- GC pause times. Modern Paper exposes this via metrics. You want average pauses under 100 ms, max pauses under 300 ms.
- Heap usage post-GC. Should sit at 30 to 60 percent of max. Higher means undersized. Lower means oversized.
If TPS is healthy and pauses are short, your flags are doing their job. Don't fiddle.
Conclusion
Aikar's flags are good defaults because they bias the JVM toward Minecraft's actual allocation pattern. They aren't magic. They're a set of reasonable choices someone made years ago, and the reasoning still holds. Knowing the reasoning lets you adapt when your workload doesn't fit the defaults.
When in doubt: G1 with Aikar's flags, heap sized to working set plus 30 percent, Xms equal to Xmx, and don't touch it unless you have telemetry telling you something is wrong.
Hosting your game server with AndroHost means we handle most of what's in this post for you automatically: tier sizing, SRV records, off-site backups, DDoS protection.
Keep reading
CPU vs GPU, a plain-English guide to how chips do math
What CPUs and GPUs actually do when they crunch numbers, why the differences matter, and how to pick the right chip for a workload.
Using your AndroHost database with plugins like LuckPerms
Every paid AndroHost server includes a real MariaDB database for plugins that need one. Here is how to set it up and connect.
Adding voice chat to your Minecraft server (Simple Voice Chat plugin)
The most requested addon for Minecraft community servers in 2026 is Simple Voice Chat (SVC) — the plugin that gives proximity-based voice (you hear players based on distance, like real life, like in DayZ or Rust). It works on Paper, Purp...