RE:NODE

Minecraft13 мин чтения

Флаги JVM для Minecraft и какую версию Java выбрать

Какая версия Java нужна каждому релизу Minecraft, как выбрать размер heap с учётом лимита контейнера, что делает каждый флаг из набора Aikar и что не делает ничего.

0 прочтений

Два решения о JVM влияют на сервер Minecraft сильнее, чем все остальные настройки вместе взятые: какую версию Java запускать и сколько выделить heap. У первого для каждой версии Minecraft есть один правильный ответ, и места для мнений нет. Второе - то, где ошибаются в обе стороны: отдают контейнеру на 2 GB heap в 2 GB и удивляются, почему он падает, или отдают серверу на двадцать игроков 16 GB и удивляются, почему паузы стали хуже.

Всё, что идёт после этих двух пунктов, - это настройка сборщика мусора, которая даёт реальное, но скромное улучшение: более ровное время тиков, а не более высокий потолок. Флаги Aikar - разумный вариант по умолчанию для этого, и так уже много лет, а в этой статье объясняется, что именно делает каждый из них, чтобы вы сами решили, оставлять ли его.

Какая версия Java для какой версии Minecraft#

Сервер Minecraft скомпилирован под конкретный релиз Java. Более новая Java почти во всех случаях запускает более старый Minecraft; более старая Java никогда не запускает более новый.

MinecraftМинимальная JavaНа практике
1.8 - 1.16.5Java 8Java 8 или 11. На новой Java часть старых плагинов ломается
1.17 - 1.17.1Java 16Java 17 работает, и её проще найти
1.18 - 1.20.4Java 17Java 21 тоже работает для Paper
1.20.5 и новееJava 21Включая всю 1.21

Если ошибиться, признак безошибочный:

code
Error: LinkageError occurred while loading main class io.papermc.paperclip.Mainjava.lang.UnsupportedClassVersionError: io/papermc/paperclip/Main has beencompiled by a more recent version of the Java Runtime (class file version 65.0),this version of the Java Runtime only recognizes class file versions up to 61.0

Class file 65 - это Java 21, 61 - Java 17, 60 - Java 16, 55 - Java 11, 52 - Java 8. Прочитайте два числа, установите то, что больше, и всё.

В обратную сторону сложнее. Запуск 1.12.2 на Java 17 или 21 часто работает, а иногда нет, потому что старые плагины и старые версии библиотек используют reflection во внутренности JDK, которые более поздние релизы закрыли. Если вы поддерживаете старый сервер, оставайтесь на той Java, с которой вышла игра. Если запускаете сборку модов, в её собственной документации указана версия Java, и ей стоит верить - об остальной части этого минного поля рассказано в статье модовый Minecraft без вылетов.

На RE:NODE версия Java подбирается под версию Minecraft при создании сервера, так что свежий сервер 1.21 уже работает на Java 21. Если вы загружаете другой jar сами, проверьте вкладку Startup, прежде чем гадать, почему он не запускается.

Какую сборку Java устанавливать#

Любая сборка OpenJDK нужной версии корректно запустит сервер Minecraft. Различия в упаковке и поддержке, а не в производительности.

  • Eclipse Temurin от Adoptium - обычный выбор по умолчанию: бесплатно, хорошо протестировано, есть для каждой LTS-версии.
  • Amazon Corretto - другой распространённый вариант с длинными сроками поддержки.
  • Microsoft Build of OpenJDK и Azul Zulu ничуть не хуже.
  • Oracle JDK работает, но идёт с лицензией, которую стоит прочитать, прежде чем использовать его в коммерческих целях.

Берите релизы с долгосрочной поддержкой - 8, 11, 17, 21, - а не промежуточные, выходящие раз в полгода, потому что авторы плагинов тестируют именно на LTS. В Linux ставьте headless-пакет (openjdk-21-jre-headless в Debian и Ubuntu), чтобы не тянуть графические библиотеки, которые сервер никогда не загрузит.

GraalVM периодически всплывает как прирост производительности. Результаты на Minecraft противоречивы: на одних нагрузках выигрыш в несколько процентов, на других проигрыш. Это не ошибка, но и не тот бесплатный апгрейд, каким его иногда описывают, так что прежде чем решиться, замерьте свой сервер.

Размер heap и разрыв между -Xmx и тарифом#

-Xms задаёт начальный heap, -Xmx - максимальный. Ставьте их одинаковыми. JVM не может с пользой возвращать память контейнеру фиксированного размера, изменение размера heap обходится паузами, а одинаковая пара означает, что график памяти что-то говорит вам, а не рисует пилу.

Многие упускают, что -Xmx - это не потребление памяти сервером. Это heap. Сверх него JVM нужны:

  • Metaspace для загруженных классов. Сервер с множеством плагинов может держать здесь 150-300 MB, а сборка модов - значительно больше.
  • Code cache для методов, скомпилированных JIT. Десятки мегабайт, растущие с аптаймом.
  • Стеки потоков, примерно по 1 MB на каждый, а потоков десятки.
  • Direct byte buffers, которые использует Netty для сети. Лежат вне heap и невидимы для -Xmx.
  • Собственные структуры GC, которые у G1 составляют заметный процент от heap.

Итого полная резидентная память сервера Minecraft - это с запасом -Xmx плюс от 400 MB до 1 GB, а на модовом сервере ещё больше. Если сумма пересекает лимит контейнера, ядро останавливает процесс, и нет ни исключения Java, ни crash-отчёта, ни намёка в логе, кроме того, что он обрывается на полуслове.

Память тарифаВыставьте -XmxОстаётся запас
2 GB1536M512 MB
4 GB3G1 GB
6 GB5G1 GB
10 GB8G2 GB
14 GB12G2 GB

Эти значения намеренно консервативны. Начните с них, неделю наблюдайте за графиком памяти при реальной нагрузке и поднимайте -Xmx, только если ограничением действительно служит heap. Поднимать его из-за того, что на графике есть свободная память, - верный способ превратить медленный сервер в постоянно перезапускающийся.

Родственная ошибка - вера в то, что больший heap означает более быстрый сервер. Это не так. Java не использует память, которую у неё не просили, а каждая сборка G1 должна просканировать и скопировать живые объекты, так что больший heap означает больше работы за цикл сборки и более длинные худшие паузы. Сервер Paper на двадцать игроков с 6 GB и разумными флагами обычно тикает лучше, чем тот же сервер с 16 GB. Цифры по числу игроков есть в статье сколько RAM нужно серверу Minecraft, а тот же довод для приложений приведён в статье лимиты памяти узла.

Флаги Aikar, строка за строкой#

Канонический набор флагов для Minecraft на G1 остаётся стабильным годами. Для heap меньше 12 GB:

bash
$ java -Xms5G -Xmx5G \    -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 \    -jar paper.jar --nogui

Для heap от 12 GB меняются пять значений: G1NewSizePercent=40, G1MaxNewSizePercent=50, G1HeapRegionSize=16M, G1ReservePercent=15, InitiatingHeapOccupancyPercent=20. Всё остальное остаётся.

Что они делают:

  • UseG1GC выбирает сборщик мусора. Он и так используется по умолчанию начиная с Java 9, но явное указание гарантирует, что смена значения по умолчанию вас не удивит.
  • ParallelRefProcEnabled обрабатывает слабые и мягкие ссылки параллельно во время паузы. Minecraft создаёт их великое множество, и это один из самых очевидных выигрышей в наборе.
  • MaxGCPauseMillis=200 - это цель, а не обещание. G1 подбирает объём работы, пытаясь в неё уложиться. Обратите внимание, что значит 200 мс для игры с тиком в 50 мс: пауза на уровне цели пропускает четыре тика. Снижение до 50 не даёт пауз в 50 мс, оно даёт гораздо более частые сборки.
  • UnlockExperimentalVMOptions нужен, потому что несколько опций G1 ниже помечены как экспериментальные.
  • DisableExplicitGC заставляет JVM игнорировать вызовы System.gc(). Плагины и библиотеки иногда их делают, и каждый такой вызов - это лишняя полная сборка с остановкой всего мира.
  • AlwaysPreTouch записывает в каждую страницу heap при запуске, чтобы память была выделена заранее и не было page fault во время игры. Расплата - более медленный старт и график памяти, который с первой секунды стоит на -Xmx, из-за чего график панели бесполезен как показатель «сколько используется».
  • G1NewSizePercent=30 и G1MaxNewSizePercent=40 увеличивают молодое поколение значительно выше 5% по умолчанию у G1. Minecraft выделяет память в огромных количествах, и большая часть умирает в течение тика-двух, так что большой eden означает меньше сборок и куда меньше продвижения в старое поколение.
  • G1HeapRegionSize=8M увеличивает регионы G1. Объекты больше половины региона считаются «humongous» и выделяются особым образом, что медленно; массивы чанков и палитр в Minecraft достаточно велики, чтобы упираться в это при размере региона по умолчанию.
  • G1ReservePercent=20 держит часть heap свободной, чтобы у эвакуации всегда было куда перемещать объекты. Без этого вы получите to-space exhaustion, которое вырождается в полную сборку.
  • G1HeapWastePercent=5 и G1MixedGCCountTarget=4 заставляют смешанные сборки начинаться раньше и завершаться за меньшее число более крупных шагов.
  • InitiatingHeapOccupancyPercent=15 запускает цикл конкурентной разметки при заполнении heap на 15% вместо 45% по умолчанию. В сочетании с большим молодым поколением это держит старое поколение чистым и в обычной работе полностью избавляет от полных сборок.
  • G1MixedGCLiveThresholdPercent=90 позволяет смешанным сборкам освобождать регионы, живые на 90% и меньше, так что ничего не остаётся навсегда несобранным.
  • G1RSetUpdatingPauseTimePercent=5 выносит обслуживание remembered set из паузы в конкурентное время.
  • SurvivorRatio=32 и MaxTenuringThreshold=1 вместе означают, что объекты либо собираются сразу, либо продвигаются после одного пережитого цикла, вместо того чтобы копироваться туда-сюда между областями survivor. Для характера выделения памяти в Minecraft такое копирование - лишняя работа.
  • PerfDisableSharedMem не даёт JVM записывать счётчики производительности в файл, отображённый в память, в /tmp. На загруженном или медленном диске эта запись способна остановить всю JVM, а файл вам не нужен ни для чего.

В опубликованной версии вы также увидите -Dusing.aikars.flags=https://mcflags.emc.gs и -Daikars.new.flags=true. Это маркеры без всякого действия, нужные лишь для того, чтобы человек, читающий crash-отчёт, видел, что флаги применялись.

Один необязательный дополнительный флаг для Java 17 и новее: --add-modules=jdk.incubator.vector позволяет Paper использовать инкубационный Vector API для части своих вычислений. При запуске JVM печатает предупреждение об использовании инкубационного модуля - это ожидаемо и ошибкой не является.

Сборщики: G1, ZGC и те, которые стоит пропустить#

G1 - ответ практически для любого сервера Minecraft. Он поколенческий, большую часть работы делает конкурентно, а набор флагов выше настроен именно под эту нагрузку.

ZGC нацелен на паузы меньше миллисекунды независимо от размера heap. В Java 21 поколенческая версия включается опцией -XX:+ZGenerational; в более новых релизах она используется по умолчанию, а непоколенческий режим уходит, поэтому перед использованием проверьте, чего ожидает ваш JDK. Он действительно помогает на очень больших heap - модовый сервер на 24 GB на машине со свободными ядрами, - но требует больше CPU и больше памяти вне heap, чем G1. Не используйте его на тарифе 4 GB с полутора ядрами: вы заплатите за конкурентность, которую не можете себе позволить.

Shenandoah, доступный в сборках Temurin и Corretto, - другой сборщик с малыми паузами, и ведёт себя похоже. Оба стоит пробовать только после того, как GC-лог докажет, что вредит именно время паузы, а не работа тика.

Parallel GC (-XX:+UseParallelGC) - сборщик, рассчитанный на пропускную способность, с длинными паузами с остановкой мира. На сервере с 1-2 GB эти паузы и так короткие, а CPU он использует меньше, чем G1, так что на самых маленьких тарифах это оправданный выбор. Выше примерно 4 GB это неподходящий инструмент.

CMS больше нет. Его удалили в Java 14, и любое руководство, рекомендующее -XX:+UseConcMarkSweepGC, написано до этого и его можно закрывать.

Флаги, которые не делают вообще ничего, но всё ещё ходят по сети: -XX:+UseFastAccessorMethods и -XX:+AggressiveOpts удалены из JVM много лет назад, -Xincgc ушёл вместе с CMS, а -XX:+OptimizeStringConcat - поведение по умолчанию уже десять лет. Задание -Xmn рядом с G1 хуже, чем бесполезно: оно закрепляет размер молодого поколения и не даёт G1 адаптироваться, что противоположно тому, чего добиваются флаги выше.

Диагностика памяти: логи, дампы и четыре ошибки нехватки памяти#

Включайте GC-логирование до того, как оно понадобится. Оно почти ничего не стоит, и это единственный способ ответить на вопрос «это подтормаживание было паузой?».

bash
-Xlog:gc*:file=logs/gc.log:time,uptime,level,tags:filecount=5,filesize=10M

На что смотреть в результате. Строки Pause Young (Normal) (G1 Evacuation Pause) в несколько десятков миллисекунд - это нормальная работа сервера. Pause Young (Concurrent Start) - начало цикла разметки, которое должно происходить регулярно и незаметно. Две строки, означающие, что что-то не так: To-space exhausted - G1 не хватило места для эвакуации, нужен больший резерв или больший heap, и Pause Full (Allocation Failure) - G1 сдался и выполнил однопоточную полную сборку. Полная сборка на heap в 6 GB - это заморозка на несколько секунд, и игроки опишут её как вылет.

Ошибки нехватки памяти - это четыре разные проблемы под одним именем:

  • java.lang.OutOfMemoryError: Java heap space - heap действительно заполнен. Либо поднимите -Xmx в пределах имеющегося запаса, либо найдите, что держит память. Сводка heap в spark называет классы.
  • java.lang.OutOfMemoryError: Metaspace - слишком много загруженных классов. На сервере с плагинами это почти всегда вызвано повторным использованием /reload, которое оставляет после себя старые загрузчики классов. Вместо этого перезапускайте как положено, а если действительно нужно больше, то -XX:MaxMetaspaceSize=512M.
  • java.lang.OutOfMemoryError: unable to create new native thread - ограничение процесса или памяти, а не проблема heap. Обычно это плагин, порождающий потоки без предела.
  • Никакой ошибки, лог просто обрывается - контейнер достиг лимита памяти и был остановлен снаружи. Java этого никак не предвидит, и поэтому существует таблица запаса.

В первом случае -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./dumps пишет дамп, который можно проанализировать. Учтите, что дамп примерно размером с heap, так что сервер на 10 GB запишет файл в 10 GB на диск, где для него может не хватить места.

Когда вы убедились, что дело не в памяти, следующая остановка - сам тик: почему падает TPS и что с этим делать, затем чтение отчёта spark, чтобы найти виновный плагин или чанк. Почему сервер может держать 20 TPS и всё равно ощущаться плохо, объясняет статья что на самом деле означает частота тиков.

Практический пример на тарифе 6 GB#

Двадцать игроков, Paper 1.21, пятнадцать плагинов, заранее сгенерированный мир с границей в 5000 блоков.

bash
$ java -Xms5G -Xmx5G -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 \    -Xlog:gc*:file=logs/gc.log:time,uptime:filecount=5,filesize=10M \    -jar paper.jar --nogui

5G из тарифа в 6 GB оставляет гигабайт на metaspace, code cache, буферы Netty и саму JVM. Java 21, потому что её требует версия Minecraft. GC-логирование включено, потому что оно бесплатно. На этом вклад JVM заканчивается - если такой сервер всё равно подтормаживает, ответ кроется в конфигурации Paper и списке плагинов, а не в очередном флаге. Дальше стоит заглянуть в руководство по оптимизации Paper, а чтение графика нагрузки сервера подскажет, на что вы смотрите: на CPU, память или диск.

На хостинге с панелью флаги вписываются в команду запуска или поле аргументов JVM, а не в shell-скрипт. На RE:NODE это вкладка Startup рядом с игровыми переменными и переменными окружения, а консоль рисует график памяти относительно лимита тарифа, так что сразу видно, не выпрямил ли AlwaysPreTouch линию на самом верху.

FAQ#

Стоит ли ставить -Xmx равным всему размеру тарифа?

Нет. Heap - лишь часть того, что использует JVM, а остальное - metaspace, code cache, стеки потоков, сетевые буферы - лежит вне его. Оставьте 500 MB на маленьком тарифе и 1-2 GB на большем, иначе контейнер будет остановлен за превышение лимита, и никакой ошибки Java, которая бы это объяснила, не будет.

Работают ли флаги Aikar на Java 21?

Да. Каждый флаг набора по-прежнему действителен в текущих LTS-релизах, а настройка по-прежнему подходит для характера выделения памяти в Minecraft. Изменилось только одно: -XX:+UseG1GC теперь просто называет значение по умолчанию, а не меняет его.

Исправит ли лаги больше RAM?

Только если не хватает именно памяти. Если график памяти держится заметно ниже лимита, а сервер подтормаживает, узкое место в тике - плагин, число сущностей или дальность прорисовки, которую CPU не вытягивает. Увеличение heap в такой ситуации делает паузы длиннее, а не короче.

Почему мой сервер всё время использует ровно максимум памяти?

Из-за -XX:+AlwaysPreTouch. Он по замыслу выделяет весь heap при запуске, так что резидентная память равна -Xmx с первой секунды. Это не утечка и не проблема, но график памяти перестаёт показывать, какая часть heap занята. Для этого пользуйтесь GC-логом или spark.

Можно ли запускать плагины для Minecraft 1.8 на Java 21?

Обычно нет. Старые плагины лезут во внутренности JDK, которые поздние релизы закрыли, и сбои получаются невнятными, а не понятными. Старый сервер должен работать на той версии Java, которая была актуальна, когда его собирали.

Важнее ли выбор сборщика мусора, чем флаги?

Для большинства серверов нет. G1 с настроенными флагами - правильный ответ примерно от 2 GB до 16 GB heap, а это покрывает почти всех. Смена сборщика - то, что стоит пробовать после того, как GC-лог покажет, что проблема именно в длительности пауз.


Комментарии

Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.

0/2000