Minecraft-Server JVM-Tuning: Garbage Collection und Aikar's Flags im Detail
Technischer Leitfaden zur Optimierung der Java Virtual Machine für Minecraft-Server: Analyse von G1GC vs. ZGC, Speicherkonfiguration und Beseitigung von Stop-The-World Latenzspitzen.
Minecraft-Server JVM-Tuning: Garbage Collection und Aikar's Flags im Detail
Die Java Virtual Machine (JVM) bildet das Fundament jedes modernen Minecraft-Servers auf Basis von Paper, Purpur oder Folia. Während die Spiel-Logik innerhalb des Minecraft-Haupt-Threads in einem festen 50-Millisekunden-Zyklus (20 Ticks pro Sekunde) abgearbeitet werden muss, erzeugt das Erstellen, Verändern und Zerstören von Block-Entitäten, Partikeln und Netzwerk-Paketen permanent kurzlebige Objekte auf dem Heap-Speicher. Eine unzureichend konfigurierte Garbage Collection führt unausweichlich zu unberechenbaren Stop-The-World (STW) Pausen, die Spieler als störende Ruckler oder Verbindungsabbrüche wahrnehmen.
In diesem Leitfaden analysieren wir die interne Funktionsweise des Garbage First Collectors (G1GC), schlüsseln die standardisierten Start-Flags auf und beleuchten fortgeschrittene Speichereinstellungen für Java 21 LTS.
1. Die Architektur der Heap-Generationen im Minecraft-Kontext
Der Java-Heap teilt sich architektonisch in zwei fundamentale Generationsbereiche: die Young Generation (unterteilt in Eden-Space und Survivor-Spaces) sowie die Old Generation (Tenured-Space). Auf einem Minecraft-Server entstehen pro Sekunde zehntausende ephemere Datenstrukturen – beispielsweise Vektorberechnungen für Projektilbahnen, temporäre Byte-Buffer des Netty-Netzwerk-Stacks und Serialisierungs-Objekte für Chunk-Pakete.
Diese Objekte sollten idealerweise innerhalb der Young Generation alloziiert, referenziert und während einer Minor Collection abgeräumt werden, ohne jemals in die Old Generation aufzusteigen. Wenn der Eden-Space zu klein bemessen ist, werden kurzlebige Objekte vorzeitig in die Old Generation befördert (Premature Tenuring). Dort akkumulieren sie, bis der Schwellenwert für eine zeitintensive Mixed Collection oder im schlimmsten Fall eine Full GC erreicht wird.
2. Aikar's Flags: Technische Dekonstruktion der Parameter
Aikar's Flags wurden über Jahre hinweg an tausenden hochfrequentierten Servernetzwerken validiert. Sie zwingen die JVM, die Speicherbereinigung in winzige, planbare Arbeitsschritte zu unterteilen, die mit dem 50ms-Tick-Budget harmonieren.
Hier ist die empfohlene Standard-Befehlszeile für Server mit 8 bis 16 GB dediziertem RAM:
java -Xms10G -Xmx10G \
-XX:+UseG1GC \
-XX:+ParallelRefProcEnabled \
-XX:MaxGCPauseMillis=200 \
-XX:+UnlockExperimentalVMOptions \
-XX:+DisableExplicitGC \
-XX:+AlwaysPreTouch \
-XX:G1NewSizePercent=30 \
-XX:G1MaxNewSizePercent=40 \
-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 \
-jar purpur-1.21.jar --nogui
Die Schlüsselparameter im Detail
-Xms10G -Xmx10G(Heap-Symmetrie): Initial- und Maximal-Heap müssen exakt identisch gesetzt werden. Eine dynamische Heap-Reskalierung zur Laufzeit erzwingt Betriebssystem-Traps, dealloziiert virtuelle Speicherseiten und löst gravierende Latenzspitzen aus.-XX:+AlwaysPreTouch: Veranlasst die JVM beim Prozessstart, sämtliche zugewiesenen Speicherseiten mit Nullen zu beschreiben. Dadurch werden physische RAM-Pages vom Linux-Kernel unmittelbar gemappt, statt später während des Spielbetriebs Page Faults zu provozieren.-XX:InitiatingHeapOccupancyPercent=15(IHOP): Definiert, dass die Hintergrund-Markierung der Old Generation bereits startet, wenn diese zu 15 % gefüllt ist. Der Standardwert von 45 % wartet viel zu lange, wodurch der Speicher vollläuft, bevor die Mixed GC fertig wird.-XX:G1ReservePercent=20: Hält 20 % des Speichers als Puffer für unvorhergesehene Allokationsschübe (z. B. wenn 30 Spieler gleichzeitig teleportieren) bereit, um Evacuation Failures abzuwenden.-XX:SurvivorRatio=32&-XX:MaxTenuringThreshold=1: Reduziert die Größe der Survivor-Spaces zugunsten eines größeren Eden-Bereichs und sorgt dafür, dass Objekte, die eine Collection überleben, zügig einsortiert werden.
3. G1GC vs. Generational ZGC: Wann lohnt sich der Wechsel?
Mit Java 21 hat das OpenJDK-Projekt Generational ZGC (-XX:+UseZGC -XX:+ZGenerational) zur Produktionsreife gebracht. Während G1GC Pausenzeiten im Bereich von 10 bis 30 Millisekunden anstrebt, arbeitet ZGC mit farbigen Pointern (Colored Pointers) und Load Barriers im concurrent Modus, wodurch Pausenzeiten unter 1 Millisekunde fallen.
| Kriterium | G1GC mit Aikar's Flags | Generational ZGC (Java 21+) |
|---|---|---|
| Pausenzeiten (STW) | 5 – 25 ms | < 1 ms |
| CPU-Overhead | Gering bis moderat | 8 – 15 % höher durch Read-Barriers |
| Mindest-Heap | Ab 4 GB effizient | Erst ab 14–16 GB stabil |
| Allokationsdurchsatz | Sehr hoch | Kann bei plötzlichen Bursts throtteln |
Für Standard-Instanzen (Citybuild, Survival, Minigames) bis 12 GB RAM bleibt G1GC dank des geringeren CPU-Overheads die überlegene Wahl. Große Proxy-Knoten oder dicht belegte Folia-Instanzen mit mehr als 24 GB RAM profitieren hingegen messbar von Generational ZGC.
4. Off-Heap-Speicher und Betriebssystem-Reserven
Ein fataler Fehler in der Serveradministration besteht darin, einer Maschine mit 16 GB Gesamtspeicher eine Startzeile von -Xmx16G mitzugeben. Der Java-Prozess benötigt neben dem Heap signifikanten Speicher für:
- Metaspace (
-XX:MaxMetaspaceSize): Enthält geladene Klassen und Plugin-Bytecode (typisch 300–600 MB). - Code Cache: Hält den vom Just-in-Time-Compiler (C2) erzeugten nativen Maschinencode (128–256 MB).
- Thread-Stacks: Jeder Java-Thread belegt standardmäßig 1 MB virtuellen Stack (
-Xss1m). Bei 150 Threads sind dies 150 MB. - Direct ByteBuffer: Netty verwendet Off-Heap-Buffer für Netzwerk-I/O.
- Linux Page Cache: Das Betriebssystem benötigt freien RAM, um Region-Dateien (
.mca) im Arbeitsspeicher zu cachen, damit Chunk-Lesezugriffe nicht auf die SSD durchschlagen.
Faustregel: Lassen Sie der Träger-Maschine mindestens 20 % des installierten Arbeitsspeichers (mindestens 2,5 GB) frei für Kernel und OS-Dienste.
5. Systematische Diagnose mit Spark-Profilern
Um den Erfolg Ihrer JVM-Konfiguration objektiv zu belegen, installieren Sie das Open-Source-Plugin Spark. Führen Sie während der Hauptverkehrszeit folgenden Befehl aus:
/spark gc
Die Ausgabe liefert Aufschluss über die durchschnittliche Dauer von Young- und Old-Generation-Pausen sowie die Allokationsrate in MB/s. Liegt die durchschnittliche GC-Pause unter 15 Millisekunden und entfallen weniger als 2 % der gesamten Laufzeit auf Garbage Collection, arbeitet das Subsystem im optimalen Bereich.
Häufig gestellte Fragen (FAQ)
Warum verursacht der Standard-Garbage-Collector von Java Lag-Spikes auf Minecraft-Servern?
Ohne spezifische Flags sammelt der Standard-Collector Generationen erst bei hohem Speicherdruck ein, was zu mehren hundert Millisekunden langen Stop-The-World Pausen führt, während derer der Haupt-Tick-Thread blockiert ist.
Kann ZGC anstelle von G1GC für Paper oder Purpur verwendet werden?
Generational ZGC (ab Java 21) erreicht Sub-Millisekunden-Pausen, benötigt jedoch mehr CPU-Ressourcen für Speicher-Barrieren. Für Server mit weniger als 12 GB Heap bleibt ein feinjustierter G1GC meist die effizientere Wahl.
Wie viel Arbeitsspeicher sollte dem Betriebssystem außerhalb des Java-Heaps verbleiben?
Mindestens 2 bis 3 Gigabyte RAM müssen für JVM-Metaspace, native Thread-Stacks, Shared Libraries und den Linux Page Cache für Chunk-I/O reserviert bleiben.