Serwer Minecraft: jak naprawić błąd „java.lang.OutOfMemoryError: Java heap space”
Komunikat java.lang.OutOfMemoryError: Java heap space nie oznacza automatycznie za małej ilości pamięci RAM. Jak poprawnie dobrać Xmx i Xms, znaleźć wycieki pamięci i sensownie wykorzystać swap.
Serwer działa od trzech godzin, po czym się zawiesza, tickrate spada do 2, a w konsoli pojawia się:
[Server thread/ERROR]: Encountered an unexpected exception
java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3537)
at it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap.rehash(...)
Pierwszy odruch jest niemal zawsze ten sam: przydzielić więcej pamięci RAM. Mniej więcej w połowie przypadków to dokładnie zła reakcja, a w części z nich sprawa robi się mierzalnie gorsza. W tym artykule pokazujemy, co ten komunikat naprawdę oznacza, jak porządnie dobrać -Xmx oraz -Xms, jak odróżnisz wyciek pamięci od zwykłego jej braku i po czym poznasz, że poprawka rzeczywiście się utrzymała.
Co dokładnie oznacza ten komunikat, a czego nie
Heap Javy to obszar, w którym JVM odkłada obiekty: wczytane chunki, encje, ekwipunki, dane pluginów. Jego górną granicę ustawiasz przez -Xmx. Komunikat OutOfMemoryError: Java heap space znaczy tyle: JVM chciała utworzyć obiekt, heap był pełny, a garbage collection nie zdołała zwolnić wystarczająco dużo miejsca. O wolnej pamięci systemu operacyjnego nie mówi to zupełnie nic. Serwer z 64 GB RAM rzuci ten komunikat równie niezawodnie, jeśli ustawiono -Xmx2G, a świat potrzebuje 4 GB.
Trzeba to wyraźnie odróżnić od dwóch innych awarii, które bardzo często się z tym myli:
- Proces znika bez stosu wywołań, w logu stoi tylko
Killed, a w journaluMain process exited, code=killed, status=9/KILL. To było jądro, nie JVM. Zadziałał linuksowy OOM killer, bo skończyła się cała pamięć systemu. Sprawdzisz to poleceniemdmesg | grep -i "out of memory", pojawi się wtedy wiersz w rodzajuOut of memory: Killed process 1337 (java). - Java w ogóle się nie uruchamia i zgłasza
Error occurred during initialization of VM / Could not reserve enough space for object heap. Wtedy-Xmxjest większe niż to, co system w ogóle jest w stanie dać.
To rozróżnienie jest najważniejszym krokiem. W pierwszym wariancie heapu jest za mało, w drugim i trzecim za dużo. Kto pomyli te przypadki, kręci nie tą śrubą, co trzeba.
Najpierw pomiar, potem przydział
Zanim zmienisz jakąkolwiek wartość, potrzebujesz dwóch liczb: faktycznie dostępnej pamięci oraz bieżącego zużycia.
free -h
cat /proc/meminfo | grep -E 'MemTotal|MemAvailable|SwapTotal'
Decyduje MemAvailable, a nie free. Linux wykorzystuje niewykorzystaną pamięć RAM jako cache plików, dlatego „free” jest prawie zawsze małe i prawie zawsze bez znaczenia.
Rzeczywiste zużycie serwera zobaczysz tak:
ps -o pid,rss,cmd -C java
Wartość RSS podawana jest w kilobajtach i oznacza pamięć, jaką proces zajmuje w RAM. Jest ona zawsze większa niż -Xmx, i dokładnie w tym miejscu większość poradników się kończy.
Dlaczego „jak najwięcej” na pewno się nie uda
Poza heapem JVM potrzebuje jeszcze całego szeregu innych obszarów pamięci, których -Xmx w ogóle nie obejmuje:
- Metaspace: wczytane klasy. Przy modpacku z 300 modami to szybko od 300 do 600 MB.
- Stosy wątków: każdy wątek dostaje około 1 MB. Workery chunków, wątki Netty, schedulery pluginów, to razem daje od 100 do 300 MB.
- Direct buffers: Netty obsługuje cały ruch sieciowy przez pamięć spoza heapu. Przy wielu graczach jednocześnie kilkaset megabajtów to norma.
- Code cache i struktury GC: kompilator JIT oraz własna księgowość G1 kosztują z grubsza od 5 do 10 procent heapu.
Sprawdzona reguła brzmi tak: licz Xmx plus od 1 do 1,5 GB dla JVM oraz co najmniej 512 MB dla systemu operacyjnego. Przy modpacku z dużą liczbą modów raczej Xmx plus 2 GB.
Na serwerze z 8 GB RAM oznacza to -Xmx6G, a nie -Xmx8G. Kto przydzieli 8, nie dostanie już błędu heapu, tylko coś gorszego: proces zestrzelony przez jądro bez ostrzeżenia, w samym środku zapisywania świata. Błąd heapu to czysta, udokumentowana awaria. Zabicie przez OOM potrafi zostawić po sobie uszkodzone pliki regionów.
Jest jeszcze drugi powód przeciwko maksymalnemu przydziałowi: zbyt duży heap spowalnia garbage collection. G1 musi przeszukać więcej pamięci, pauzy przy mixed GC się wydłużają, a z okazjonalnego przycięcia robi się wyraźne zawieszenie. Powyżej mniej więcej 12 GB bilans przy Minecrafcie z reguły przechyla się na minus. Kto potrzebuje więcej, powinien podzielić świat, zamiast powiększać heap.
Orientacyjne wartości według liczby graczy i modpacka
Te wartości to punkty wyjścia, a nie prawa natury. Zakładają świat normalnej wielkości oraz wykonane wcześniej pregenerowanie terenu.
| Typ serwera | Gracze | Xmx | RAM w systemie |
|---|---|---|---|
| Vanilla lub Paper, bez pluginów | do 10 | 2G | 4 GB |
| Paper, od 15 do 30 pluginów | od 10 do 30 | 4G | 8 GB |
| Paper, duży zestaw pluginów, baza danych | od 30 do 80 | 6G do 8G | 12 do 16 GB |
| Lekki modpack, do 120 modów | do 10 | 6G | 8 GB |
| Średni modpack, od 150 do 250 modów | do 20 | 8G do 10G | 16 GB |
| Ciężki modpack, od 300 modów | do 20 | 10G do 12G | 16 do 24 GB |
| Proxy (Velocity, BungeeCord) | dowolna liczba | 512M do 1G | 2 GB |
Dwie uwagi do tego. Po pierwsze, przy modpackach zapotrzebowanie na heap skaluje się niemal wyłącznie z liczbą modów i wielkością świata, prawie wcale z liczbą graczy. Po drugie, view-distance w pliku server.properties to najskuteczniejsza dźwignia w ogóle: zejście z 10 na 8 oszczędza często więcej pamięci niż 2 GB dodatkowego heapu, bo liczba wczytanych chunków rośnie z zasięgiem widzenia kwadratowo. Ustawienie simulation-distance na 6 działa dodatkowo na obciążenie CPU.
Xms równe Xmx: rozgrzewka
-Xms określa, z jak dużym heapem startuje JVM. Jeśli stoi tam wartość mniejsza niż przy -Xmx, heap rośnie w trakcie pracy kawałek po kawałku. Każde powiększenie oznacza pełne zbieranie garbage collection oraz świeże page faulty po stronie systemu operacyjnego, a ponieważ G1 pozwala heapowi także się kurczyć, cała zabawa powtarza się od nowa. Stąd właśnie biorą się osławione przycięcia co kilka minut w pierwszych godzinach po starcie.
Ustawiaj -Xms zawsze równe -Xmx. W połączeniu z -XX:+AlwaysPreTouch JVM przy starcie dotyka raz każdej pojedynczej strony pamięci heapu. Start trwa przez to od 2 do 15 sekund dłużej, zależnie od wielkości heapu, za to w czasie gry nie zdarzają się już żadne page faulty. Przyjemny efekt uboczny: jeśli -Xmx dobrano zbyt hojnie, z reguły widać to już przy starcie, a nie dopiero trzy godziny później w środku rozgrywki.
Nie polegaj przy tym na krótkim teście z -version. Sprawdzone w praktyce: java -Xms4G -Xmx4G -XX:+AlwaysPreTouch -version przeszło bez zarzutu w środowisku ograniczonym do 2 GB, bo wywołanie kończy się, zanim heap faktycznie zostanie użyty. Czystym dowodem jest dopiero prawdziwy start serwera i późniejsza obserwacja przez ps -o rss= -C java.
Sprawdzone polecenie startowe dla Papera wygląda więc tak (tak zwane flagi Aikara):
java -Xms6G -Xmx6G \
-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 \
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/minecraft/dumps \
-XX:+ExitOnOutOfMemoryError \
-jar paper.jar nogui
Od 12 GB heapu PaperMC zaleca zmienione wartości: G1NewSizePercent=40, G1MaxNewSizePercent=50, G1HeapRegionSize=16M, G1ReservePercent=15 oraz InitiatingHeapOccupancyPercent=20.
Dwa ostatnie wiersze to właściwy zysk i brakuje ich w niemal każdym poradniku. -XX:+HeapDumpOnOutOfMemoryError zapisuje przy awarii pełny obraz pamięci, na którym da się później udowodnić przyczynę. -XX:+ExitOnOutOfMemoryError kończy JVM natychmiast, zamiast zostawiać ją w półżywym stanie, w którym gracze się łączą i tracą postępy. Razem z Restart=on-failure w pliku unitu daje to czysty restart, zobacz Automatyczne uruchamianie serwera Minecraft oraz Tworzenie usługi systemd.
Weź to pod uwagę: katalog na zrzuty musi istnieć i mieć co najmniej tyle miejsca, ile wynosi -Xmx. Heap o wielkości 8 GB tworzy plik .hprof o wielkości 8 GB. Jeśli dysk się potem zapełni, masz drugi problem, zobacz Pełny dysk w systemie Linux.
Wersja Javy i różnice między systemami
To, której JVM używasz, wyraźnie zmienia zachowanie:
- Java 8 używa domyślnie kolektora Parallel, nie G1. Tu pojawia się też wariant
java.lang.OutOfMemoryError: GC overhead limit exceeded, który oznacza, że ponad 98 procent czasu poszło na garbage collection. Logowanie GC włącza się przez-XX:+PrintGCDetails -Xloggc:gc.log. - Od Javy 9 G1 jest standardem na wszystkich maszynach z co najmniej dwoma rdzeniami i 1792 MB RAM. Logowanie idzie przez nowe unified logging:
-Xlog:gc*:file=logs/gc.log:time,uptime:filecount=5,filesize=10M. Stare flagi są tu odrzucane, serwer się nie uruchamia. - Sytuacja z pakietami: Debian 13 dostarcza
openjdk-21-jre-headlessorazopenjdk-25-jre-headless, ale żadnej Javy 17. Debian 12 dostarcza Javę 17, ale ani 21, ani 25. Ubuntu 22.04 i 24.04 mają 8, 11, 17, 21 oraz 25. Kto potrzebuje konkretnej wersji, której dystrybucja nie zna, sięga po repozytorium Adoptium (Temurin od 8 do 26 dla trixie, bookworm, noble i jammy). Szczegóły w Instalacja Javy 21 na Debianie oraz Instalacja Javy 17 na Debianie.
Pułapka, w którą wpada niemal każdy: narzędzia diagnostyczne jcmd, jmap, jstat oraz jstack w pakietach jre-headless nie występują. Siedzą w openjdk-XX-jdk-headless. Kto prowadzi serwer na samym JRE, w razie awarii zostaje bez narzędzi:
apt install -y openjdk-21-jdk-headless
dnf install -y java-21-openjdk-devel
Pierwszy wiersz dotyczy Debiana i Ubuntu, drugi AlmaLinuksa, Rocky Linuksa oraz Oracle Linuksa. Potem jcmd i jmap leżą w /usr/bin/. Sprawdź to poleceniem command -v jcmd, a nie which jcmd: w instalacji minimalnej rodziny Red Hat brakuje which, a w EL 10 jest on i tak wycofany.
Jak rozpoznać wycieki pamięci powodowane przez pluginy
Wyciek pamięci wygląda inaczej niż zwykły jej brak. Różnica tkwi w przebiegu: przy zbyt małym heapie zużycie po starcie ustala się na wysokim poziomie i tam zostaje. Przy wycieku wartość bazowa rośnie dalej po każdym pełnym zbieraniu garbage collection. To właśnie ta wartość bazowa jest wskaźnikiem, który się liczy.
Da się ją odczytać bezpośrednio. Najpierw ustal identyfikator procesu, potem wymuś pełne zbieranie i obejrzyj heap:
pgrep -f paper.jar
jmap -histo:live PID | head -30
jcmd PID GC.heap_info
Ważna uwaga: jmap -histo:live wyzwala wewnętrznie pełne zbieranie i działa również wtedy, gdy ustawiono jak wyżej -XX:+DisableExplicitGC. Nasuwające się polecenie jcmd PID GC.run w tej konfiguracji nie robi nic, bo idzie przez System.gc(), a dokładnie to zostało wyłączone. Z doświadczenia kosztuje to pół godziny zamieszania.
Zapisz wartość zaraz po starcie, a potem po dwóch, sześciu i dwunastu godzinach. Jeśli wartość po pełnym zbieraniu stale rośnie, choć na serwerze nie ma więcej graczy, to wyciek. Lista klas z histogramu zwykle już pokazuje, w którą stronę to idzie: masowo ItemStack, CraftPlayer po dawno wylogowanych graczach albo obiekt z nazwą pakietu jakiegoś pluginu.
Znacznie wygodniej robi się to pluginem spark, dostępnym dla Papera, Fabrica oraz Forge:
/spark healthreport --memorypokazuje na jednym ekranie wykorzystanie heapu, zachowanie GC oraz obszary spoza heapu./spark heapsummarytworzy ranking klas według zużycia pamięci, nie zapisując przy tym zrzutu o wielkości kilku gigabajtów./spark gcpokazuje częstotliwość i czas trwania zbierania.
Jeśli podejrzenie pada na konkretny plugin, sprawdzenie jest proste: usuń plugin, zostaw serwer na 24 godziny, zmierz wartość bazową jeszcze raz. Klasyczni kandydaci to pluginy, które trzymają dane graczy w mapach i nie sprzątają ich przy wylogowaniu, oraz wszystko, co zajmuje się edycją świata i prowadzi nieograniczoną historię cofania.
Swap: koło ratunkowe, a nie powiększenie pamięci
Swap i heap Javy to naturalni wrogowie. Garbage collection regularnie dotyka dużych fragmentów heapu. Jeśli choćby ułamek z nich leży na dysku, z pauzy 50 milisekund robi się pauza 20 sekund i serwer uchodzi za zawieszony. Nigdy nie wliczaj swapa do wielkości heapu.
Mimo to swap powinien istnieć, tylko mały i leniwy. Działa jak bufor, żeby krótkie skoki nie uruchamiały od razu OOM killera, i przyjmuje rzadko używane strony innych usług. Zalecane są 2 do 4 GB oraz niska skłonność do wymiany:
swapon --show
cat /proc/sys/vm/swappiness
sysctl -w vm.swappiness=10
Na stałe ustawia się to w pliku /etc/sysctl.d/99-swappiness.conf. Cała konfiguracja krok po kroku opisana jest w Konfiguracja swapa i zapobieganie brakom pamięci.
Jeden przypadek szczególny zasługuje na uwagę: -XX:+AlwaysPreTouch w połączeniu ze zbyt małą ilością RAM. Ponieważ przy starcie dotykana jest każda strona heapu, nadmiar od razu wędruje do swapa. Serwer wprawdzie wystartuje, ale od pierwszego ticku jest bezużytecznie wolny. Jeśli serwer po włączeniu PreTouch nagle startuje ekstremalnie ociężale, winne jest zbyt duże -Xmx, a nie PreTouch.
Jako twardą granicę można dodatkowo ustawić MemoryMax w unicie systemd, na przykład MemoryMax=7G przy 8 GB RAM. Wtedy przy wykolejeniu jądro trafia celowo w proces Minecrafta, a nie w bazę danych czy dostęp SSH.
Po czym poznasz, że problem naprawdę zniknął
Restart bez natychmiastowej awarii to żaden dowód. Sprawdź zamiast tego te pięć punktów po 24 godzinach pracy przy normalnym obciążeniu:
jcmd PID GC.heap_info: zajęty heap zaraz po pełnym zbieraniu powinien leżeć wyraźnie poniżej 70 procent wartości-Xmxi pozostawać stabilny w czasie.ps -o rss= -C java: wartość powinna ustabilizować się mniej więcej naXmxplus 1 do 1,5 GB i dalej nie rosnąć. Jeśli rośnie, a heap pozostaje stabilny, wyciek jest poza heapem, typowo w metaspace albo w direct buffers.free -h: available nie powinno nigdy spaść poniżej mniej więcej 500 MB.swapon --show: zajęta ilość swapa powinna pozostawać bliska zeru.- Log GC: pełne cykle zbierania („Pause Full”) praktycznie nie powinny się zdarzać, a zwykłe pauzy powinny zostawać poniżej 200 milisekund. Pełne GC występujące seriami, jedno po drugim, to pewna zapowiedź kolejnego
OutOfMemoryError, często z kwadransowym wyprzedzeniem.
Dodatkowo /tps lub /spark tps pokazuje w grze, czy tickrate stoi stabilnie na 20,0. Serwer zdrowy pamięciowo utrzymuje tę wartość również po wielu godzinach.
Kiedy Java w ogóle nie startuje
Cztery komunikaty w oryginalnym brzmieniu, które typowo pojawiają się przy zmienianiu wartości pamięci:
Invalid maximum heap size: -Xmx8GBto najczęstsza literówka. Jednostka nazywa sięG, nieGB. Dozwolone sąk,morazg, pisane wielką albo małą literą.Initial heap size set to a larger value than the maximum heap sizeoznacza, że-Xmsjest większe niż-Xmx, najczęściej dlatego, że przy kopiowaniu poprawiono tylko jedną z dwóch wartości.Could not reserve enough space for object heapznaczy, że przydział przekracza dostępną pamięć. Na 32-bitowej JVM granica leży w każdym przypadku tuż poniżej 4 GB, niezależnie od zainstalowanej pamięci RAM. Sprawdzisz to poleceniemjava -version, musi tam stać64-Bit Server VM.Unrecognized VM option 'UseG1GC'albo podobny komunikat wskazuje na zbyt starą lub niewłaściwą JVM. Część flag eksperymentalnych wymaga koniecznie poprzedzającego-XX:+UnlockExperimentalVMOptions, i to przed daną flagą w wierszu poleceń.
To, z czym JVM ostatecznie pracuje, da się w każdej chwili zweryfikować. Bez działającego serwera, po wartościach domyślnych:
java -XX:+PrintFlagsFinal -version 2>/dev/null | grep -w MaxHeapSize
Dwa drobiazgi są tu celowe. 2>/dev/null połyka baner wersji, który JVM wypisuje na wyjście błędów i który inaczej trafiłby do wyniku nieprzefiltrowany, obok grepa. A grep -w MaxHeapSize zamiast grep -i maxheapsize daje naprawdę tylko jeden wiersz: nieostre wyszukiwanie znajduje dodatkowo SoftMaxHeapSize, więc kto spodziewa się jednej wartości, łatwo odczyta tę niewłaściwą.
A na działającym procesie, po faktycznie aktywnych flagach:
jcmd PID VM.flags
To najpewniejszy sposób, żeby wykryć zaskakująco częstą sytuację, w której skrypt startowy wprawdzie zmieniono, ale serwer nadal działa ze starymi wartościami z drugiego pliku skryptu.
Podsumowując: najpierw zmierz, czy problemem w ogóle jest heap. Potem przydziel tyle, ile potrzebuje świat, i zostaw co najmniej 1,5 GB dla JVM oraz systemu. Ustaw -Xms równe -Xmx, włącz zrzut heapu na wypadek awarii i obserwuj wartość bazową po pełnym zbieraniu przez kilka godzin. Różnica między „działa” a „działa stabilnie” leży dokładnie w tym ostatnim kroku.
Podstawową konfigurację samego serwera znajdziesz w Instalacja serwera Minecraft na Debianie oraz Lista kontrolna dla nowego serwera root.
Najczęstsze pytania
Ile pamięci RAM przydzielić serwerowi Minecraft?
Dlaczego Xms ma być równe Xmx?
Czym różni się OutOfMemoryError od procesu, który po prostu znika z komunikatem Killed?
Jak rozpoznać wyciek pamięci powodowany przez plugin?
Czy więcej swapa pomoże na błąd Java heap space?
Dlaczego nie znajduję jcmd ani jmap na swoim serwerze?
2026 KernelHost GmbH. Wszelkie prawa zastrzeżone. Ten poradnik jest chroniony prawem autorskim. Publikowanie go w innych serwisach, w całości, we fragmentach lub w zmienionej formie, wymaga naszej pisemnej zgody. Cytaty z podaniem źródła i z linkiem są jak najbardziej mile widziane.

