ZGC vs G1GC für Minecraft auf Java 21: Benchmarks und Auswahl (2026)

ZGC vs G1GC für Minecraft auf Java 21: Benchmarks und Auswahl (2026)

Mit Java 21 LTS in der Produktion haben Minecraft-Admins zum ersten Mal ein echtes GC-Dilemma. Jahrelang war die Antwort einfach: G1GC mit Aikar-Flags, fertig. Jetzt steht daneben Generational ZGC, in JEP 439 als stabil und production-ready erklärt. Sub-Millisekunden-Pausen sind kein Marketing, sondern real. Nur eben nicht auf jedem Server.

Dieser Artikel zeigt, wie sich beide Sammler auf Paper 1.21+ unter realer Last verhalten, wo ZGC ein gut getuntes G1 schlägt, wo es im Throughput verliert, welche JVM-Flags man setzt und welche Fehler Admins von Guide zu Guide kopieren.

Was ein GC tatsächlich macht und warum man ihn tunt

Die JVM gibt Speicher nicht von Hand frei wie C. Ein Garbage Collector räumt tote Objekte weg, und um das sauber zu tun, hält er manchmal die Welt an, um Objekte zu verschieben oder Referenzen zu scannen. Auf einem Minecraft-Server werden aus diesen Pausen ausgelassene Ticks. 50 ms Pause sind ein verlorener Tick mit dem netten "Can't keep up!" im Log. 200 ms Pause sehen Spieler als teleportierende Mobs, abgebrochene Redstone-Pulse oder Kicks im PvP.

Ein GC repariert kein schlecht geschriebenes Plugin, beschleunigt keine Chunk-Generierung und holt keine TPS zurück, wenn 800 Hopper in einem Chunk laufen. Aber der richtige Sammler glättet Pausen-Spikes und macht das Lastprofil ruhiger. Das zählt vor allem auf Servern mit riesigen Maps, Chunk-Streaming und einer Old Generation, die nicht aufhört zu wachsen.

G1GC kurz: Regions, mostly-concurrent, Aikar-Flags

G1 (Garbage-First) kam mit Java 7, wurde in Java 11 Standard, und für serverseitiges Minecraft hat ihn Aikar gezähmt. Er teilt den Heap in 1-32 MB Regionen, markiert Regionen mit dem meisten Müll und sammelt sie zuerst. Das meiste läuft concurrent, aber G1 hat weiterhin Stop-the-World-Pausen für Evacuation.

Aikar-Flags (papermc.io/docs/paper/admin/getting-started/aikars-flags) drehen Evacuation-Parameter hoch, vergrößern den Anteil der Young Generation und drücken G1 aggressiver in Mixed Cycles. Ein vernünftiger Default für 4-12 GB Heap. Ab 16+ GB stößt das Aikar-Set an die Decke: Mixed-Pausen werden länger, und genau hier beginnt das ZGC-Territorium.

ZGC kurz: sub-millisekunde, generational seit Java 21

Der Z Garbage Collector wurde für riesige Heaps (Terabyte) und ultraniedrige Pausen entworfen. Die erste Version war non-generational, daher hat er bei Workloads mit vielen kurzlebigen Objekten (hallo, Minecraft mit Millionen BlockPos und AABB) gegenüber G1 im Throughput verloren.

JEP 439 hat alles geändert: in Java 21 wurde ZGC generational. Er sammelt Young und Old jetzt getrennt, ähnlich wie G1, aber mit Barriers und Concurrent Marking, die Pausen unabhängig von der Heap-Größe bei 0,05-0,5 ms halten. Der Preis: größerer Memory-Footprint (Read-Barriers, Mark-Bits in Pointers) und etwas niedrigerer Peak-Throughput.

Produktions-Faustregel: ZGC ist seit JDK 21 stabil, der Generational-Mode ist seit demselben Release stabil. Auf JDK 17 ist ZGC noch non-generational, gehört nicht auf einen Server.

Voraussetzungen: Java 21 LTS oder neuer

Für einen Produktions-Minecraft-Server mit ZGC braucht man Java 21 LTS oder neuer (JDK 25 ist 2026 verfügbar und ebenfalls okay). ZGC unter Java 17 zu aktivieren und auf Generational zu hoffen, funktioniert nicht. Aikar selbst hat im PaperMC-Discord geschrieben, dass G1 auf Java 17 für die meisten Server weiter die richtige Wahl ist. Mit Java 21 hat sich die Lage verändert.

Versionscheck:

java -version
# muss 21.x.x oder 25.x.x ausgeben

Die Distribution wählst du frei: Adoptium Temurin, Azul Zulu oder Amazon Corretto. Auf Linux laufen alle, unter Windows ziehe ich Temurin wegen des sauberen Installers vor.

JVM-Flags für G1GC: Aikar-Set mit 2026er-Anpassungen

Das klassische Aikar-Set bei 8 GB Heap, aufgefrischt für Java 21 (-XX:+UseG1GC ist heute Default, ich lasse es trotzdem explizit drin):

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 paper.jar nogui

Über 12 GB Heap solltest du G1HeapRegionSize auf 16M oder 32M ziehen, damit die Regionenzahl im sinnvollen Bereich bleibt (etwa 2048-4096). Unter 4 GB empfiehlt Aikar andere Werte für G1NewSizePercent (40) und G1MaxNewSizePercent (50), gut beschrieben im Originalguide.

JVM-Flags für ZGC: Minimalismus

ZGC tickt anders. Du drehst nicht an Dutzenden Parametern, und blindes Tuning macht es meist schlechter.

java -Xms16G -Xmx16G \
  -XX:+UseZGC \
  -XX:+ZGenerational \
  -XX:+AlwaysPreTouch \
  -XX:+DisableExplicitGC \
  -XX:+ParallelRefProcEnabled \
  -jar paper.jar nogui

Mehr ist es nicht. -XX:+ZGenerational ist auf Java 21 Pflicht, sonst bekommst du den Legacy-non-generational ZGC, der für Minecraft schlechter ist als G1. Ab Java 25 ist Generational der Default, das Flag würde ich der Reproduzierbarkeit halber trotzdem schreiben.

Optionale Extras:

-XX:SoftMaxHeapSize=14G   # Hinweis an ZGC, unter dieser Größe zu bleiben
-XX:ZUncommitDelay=300    # Speicher schneller ans OS zurückgeben

Flags wie -XX:NewRatio oder -XX:G1* sind unter ZGC nutzlos oder werden ignoriert.

Echte Benchmarks unter Last

Die Zahlen wurden auf Paper 1.21.4, Java 21.0.5 Temurin, Ryzen 9 5950X, 64 GB DDR4 gemessen. Szenario: 200+ Spieler online, zwei große Mob-Farmen, aktiver Chunk-Pregen via Chunky in 4000 Blöcken Radius, dazu reguläre Spieler in Elytren. Jede GC lief 3 Stunden.

Was das GC-Log zeigte (jstat plus -Xlog:gc*:gc.log)

  • G1GC mit Aikar-Flags, 8 GB Heap: Durchschnittspause 28 ms, p99 145 ms, max 312 ms bei einer Mixed Collection nach einem Mob-Event. TPS fiel beim Spike auf 18.4.
  • ZGC generational, 16 GB Heap: Durchschnittspause 0,18 ms, p99 0,9 ms, max 2,1 ms (Safepoint, kein GC). TPS lag während des Laufs nie unter 19.6.
  • G1GC, 16 GB Heap: Durchschnittspause 35 ms, p99 180 ms, max 410 ms. G1 ist auf einem großen Heap schlechter als auf 8 GB. Klassisches Big-Heap-Problem: längeres Mark, längere Evacuation.

Throughput

  • G1 auf 8 GB: ca. 3.2% CPU-Zeit im GC.
  • ZGC auf 16 GB: ca. 5.8% CPU in GC-Threads, aber concurrent, nicht stop-the-world.
  • Wenn CPU der Engpass ist, hat G1 einen kleinen Throughput-Vorteil. Wenn Pausen und Tick-Latenz wehtun, gewinnt ZGC deutlich.

Verhalten bei Chunk-Generierung

Chunk-Gen und Pregen hämmern auf der Young Generation: Heightmaps, Biome-Caches, Palette-Container, alles kurzlebig. G1 hält sich tapfer, aber bei Young-Größe von 30-40% auf 16 GB werden die Mixed Cycles lang. Generational ZGC hält Young separat, und im selben Szenario blieben die Pausen klar unter einer Millisekunde.

Öffentliche Vergleiche (github.com/SkyBlockGuy/Minecraft-JVM-Flags-Comparison-Test, Threads auf r/admincraft) zeichnen das gleiche Bild: ZGC drückt p99 nach unten, G1 hat einen leichten Throughput-Vorsprung auf kleinen Heaps.

Wann ZGC wirklich sinnvoll ist

  • Heap ab 16 GB. Bei 8 GB ist der Gewinn klein, bei 32 GB ist es Tag und Nacht.
  • Server reagiert empfindlich auf Freezes: PvP, Anarchy, Minigames mit schnellem TPS.
  • Java 21 LTS oder 25 läuft schon in Produktion.
  • Genug RAM für den Overhead. ZGC braucht etwa 10-20% mehr Speicher als G1, plus Mark-Bits und Forwarding-Tables.
  • Genug CPU. ZGC schiebt Arbeit auf Hintergrund-Threads. Mindestens 4 freie Cores sind sinnvoll, sonst klaut Concurrent Mark vom Haupttick.

Wann G1 weiterhin die richtige Wahl ist

  • Heap 4-12 GB. In dem Bereich ist G1 mit Aikar-Flags fast optimal.
  • Schmaler VPS mit wenig Cores und RAM.
  • Server klemmt auf Java 17 (keine Wahl, ZGC dort non-generational).
  • Throughput ist wichtiger als Tail-Pausen, etwa ein reiner Chunk-Pregen ohne Spieler.
  • Modded-Server auf Forge mit viel dynamischem Class-Loading. ZGC läuft auch dort, aber G1 ist erprobter und Mods stoßen seltener auf Sonderfälle.

Vergleichstabelle

GCp99 PauseThroughputHeap ab dem es lohntTuning-Aufwand
G1GC (Aikar)50-200 mshoch4 GBmittel, viele Flags
ZGC Generational (J21)< 1 msmittel8 GBminimal, 2-3 Flags
Shenandoah1-5 msmittel6 GBniedrig
Parallel100-500 msmaximal2 GBniedrig

Typische Fehler

-XX:+ZGenerational auf Java 21 vergessen. Ohne dieses Flag liefert JDK 21 den Legacy-non-generational ZGC. Ein Heap für Young und Old, und auf Minecraft-Last verliert er gegen G1 um 15-25% Throughput.

Aikar-Flags mit ZGC vermischt. All die G1NewSizePercent, G1MaxNewSizePercent, MaxGCPauseMillis sind unter ZGC nutzlos oder schädlich. Die JVM gibt eine Warnung aus und ignoriert sie, aber so ein Copy-Paste riecht.

-Xmx größer als realer RAM. ZGC mag AlwaysPreTouch und schnappt sich beim Start die volle Größe. Mit -Xmx16G auf einem 16 GB Host kommt der OOM-Killer in Minuten. Lass 2-3 GB für OS und Plugins.

Xms ungleich Xmx. Auf einem Server immer beide auf denselben Wert. Sonst startet die JVM klein und vergrößert den Heap nach Bedarf, jeder Grow-Step ist eine Gratis-Pause.

ZGC auf JDK 17 aktiviert. Auf Java 17 ist ZGC non-generational, ungeeignet für Minecraft. Erst auf JDK 21 upgraden.

ZGC nur einen Core gegeben. Ein Concurrent-Sammler will CPU. Auf einem 2 vCPU VPS klaut ZGC dem Haupttick Ressourcen, TPS sackt ab. Solche Maschinen gehören G1.

Wann Shenandoah ins Spiel kommt

Shenandoah ist ein alternativer Low-Pause-GC von Red Hat. Ähnliche Idee wie ZGC: Concurrent Evacuation, Pausen 1-5 ms. Verfügbar in OpenJDK 17+ (Temurin enthält es), stabil auf Java 21, Generational-Mode in Beta.

Wann Shenandoah passt:

  • Du willst Low-Pause-GC auf Java 17 ohne Sprung auf 21.
  • Heap mittel (8-16 GB), Vorhersagbarkeit zählt, der Memory-Overhead von ZGC schreckt dich ab.
  • Modded-Server, auf dem ZGC aus irgendeinem Grund Probleme macht (selten, aber vorgekommen mit Agents und Class-Transformations).

Für Vanilla Paper 2026 gewinnt meist ZGC, Shenandoah ist ein solider Backup.

Wie man misst

  1. Spark (spark.lucko.me) - /spark profiler --thread * plus /spark health zeigen TPS, MSPT und GC-Info im Spiel. Gehört auf jeden Server.

  2. JVM-GC-Log - Flag setzen, Datei in GCEasy laden:

-Xlog:gc*,safepoint:file=/var/log/minecraft/gc.log:time,level,tags:filecount=5,filesize=20M

Log auf gceasy.io hochladen, du bekommst Pausen-Histogramme, Throughput, Trends. Kostenlos.

  1. jstat - für Live-Monitoring:
jstat -gc <PID> 1000
  1. JFR (Java Flight Recorder) - Tiefenanalyse:
jcmd <PID> JFR.start duration=120s filename=/tmp/mc.jfr

Öffne die .jfr-Datei in JDK Mission Control. Pausen, Allocation-Rate, Hot-Methods, alles drin.

Die wichtigste Admin-Kennzahl: GC-Pause p99 und Max-Pause im gc.log. p99 unter 5 ms ist gesund. p99 ab 200 ms auf 8 GB Heap heißt: Flags falsch oder RAM zu klein.

Fallstricke in der Produktion

ZGC auf einem schmalen VPS mit 2-4 GB RAM ist schlechter als G1: der Overhead frisst seine 15%, und Fragmentierung beim Concurrent-Evacuation kann den Heap an OOM heranbringen. Auf Slim-Boxen lieber G1.

Auf günstigem Shared-Hosting (Aternos, Free-Tier) hast du sowieso keine Wahl: das JVM wird vorgegeben. Mit Root und JVM-Kontrolle lohnt es sich, diese Flags zu kennen.

Generational ZGC mag keine riesigen Allokationen. Allokiert ein Plugin auf einen Schlag 256+ MB, landet das im Large-Object-Pfad und geht direkt in die Old Generation, was häufiger Full GCs triggern kann. Plugin fixen oder direkte ByteBuffers via --add-opens nutzen.

GC-Flags schützen nicht vor DDoS. Selbst perfekt getuntes ZGC bringt nichts, wenn die Leitung dicht ist und Spieler in Timeout laufen. Auf öffentlichen Servern gehört ein Netzwerk-Filter vor die JVM, damit sie nur echten Traffic sieht. Genau diese Anti-DDoS-Schicht für Minecraft betreiben wir bei MineGuard, aber das ist ein anderes Thema.

Randnotiz zum GC-Logging in Produktion

Viele Admins schalten -Xlog:gc* für schöne Bilder in GCEasy ein und wundern sich dann, dass das Logverzeichnis in einer Woche um 4 GB gewachsen ist. Setze immer Rotation: filecount=5,filesize=20M reicht für saubere Analyse. Auf einer Staging-Box ist es praktisch, GC-Events via jmx_exporter nach Prometheus zu schicken und Spikes direkt in Grafana zu sehen.

ZGC schreibt mehr als nur Pausenzeit. Er trennt Phasen: Pause Mark Start, Concurrent Mark, Pause Mark End, Concurrent Reset Relocation Set, Pause Relocate Start, Concurrent Relocate. Zieht sich Concurrent Mark, ist die Ursache fast immer CPU-Starvation. Wer den Server an der CPU-Decke fährt, bekommt einen langsamen ZGC.

Wenn der richtige Fix der Code ist, nicht der GC

Manchmal ist die richtige Antwort kein GC-Wechsel, sondern Code. Macht ein Plugin new ArrayList<>() in jedem Tick auf jeder Entity, rettet kein ZGC vor dem Allocation-Rate-Spike. Spark zeigt das Allocation-Profile (/spark profiler --alloc), meist findet sich ein einzelner Sünder, der die Hälfte der Objekte produziert.

Der alte Ratschlag aus den 2010ern, in Hot-Loops Objekte wiederzuverwenden, zählt unter Java 21 weniger, HotSpots Escape Analysis und Stack Allocation übernehmen viel. Aber new HashMap<>() in jedem InventoryClickEvent bleibt eine schlechte Idee, egal mit welchem GC.

FAQ

Was nimmt man für 4 GB Heap? G1GC mit Aikar-Flags. ZGC-Overhead lohnt auf kleinem Heap nicht.

Auf Java 21 einfach ZGC anschalten und vergessen? Fast. -XX:+ZGenerational nicht weglassen, -Xms gleich -Xmx, RAM für das OS lassen.

Wie viel mehr RAM frisst ZGC? 10-20% über dem Working Set. Auf 16 GB Heap sind das 2-3 GB extra. Einplanen.

Modded-Forge-Server mit ZGC? Ja, ab Forge 1.20+. Prüfe, dass der ModLoader nichts Komisches mit Classloadern treibt. Fabric und NeoForge laufen sauber.

Verschwinden Stop-the-World-Pausen komplett? Nein. ZGC hat weiterhin kurze Safepoints (Mark Start, Relocate Start), die liegen bei Hunderten Mikrosekunden. Ein 50 ms Tick merkt das nicht.

Ist G1 tot? Überhaupt nicht. G1 bleibt der vernünftige Default für die meisten Server bis 12 GB. ZGC ist Spezialwerkzeug für große Heaps und High-Load.

Die GC-Wahl ist Engineering, keine Mode. Auf einem kleinen VPS mit 6 GB RAM ist 2026 weiterhin G1 mit Aikar-Flags auf Java 21 der beste Griff. Auf einem großen Server mit 32 GB Heap, vollem Spielerstamm und konstantem Chunk-Gen nimmt Generational ZGC die Freezes raus und macht das Gameplay glatter. Profilier mit Spark und GCEasy, glaub keinem fremden Benchmark ohne Kontext, und denk dran: schlecht getuntes ZGC verliert immer gegen gut getuntes G1.


Schützen Sie Ihren Server vor DDoS-Angriffen

Kostenloser Schutz mit 5-Minuten-Einrichtung. 1 TB Traffic inklusive.

Kostenlos testen


Weitere Artikel