From 42f48fea43ee1852401a45cc1693549cc3364713 Mon Sep 17 00:00:00 2001 From: SashegDev Date: Sun, 4 Oct 2026 13:55:14 +0000 Subject: [PATCH] perf(config): document and justify the heap-shrink flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлен -XX:MaxHeapFreeRatio=30 -XX:MinHeapFreeRatio=10 в start.sh на проде и зафиксирован в README с замерами, потому что флаги JVM не переносятся копированием конфигов — при переезде они теряются молча. Замер, мир 18 МБ, 0 игроков, 100 с простоя: коммит живые RSS без флагов 630 МБ 278 МБ 0.99 ГБ с флагами 389 МБ 274 МБ 0.69 ГБ Минус 300 МБ RSS без единого ручного GC. Причина была не в отсутствии Full GC: jcmd GC.run отрабатывает, живые падают 289 -> 248 МБ, но коммит остаётся на 725 МБ. G1 сжимает кучу только когда свободно выше MaxHeapFreeRatio, а он по умолчанию 70. При 630 МБ коммита свободно было 56% — ниже порога, и 350 МБ держались вечно. Паузы Mixed (то, что ест кадр в рантайме) не ухудшились: median 11.0 -> 9.8, p95 28.3 -> 28.1, max 38.1 -> 28.7 мс. Concurrent-циклов стало больше (11 -> 18), общий p95 по всем паузам в одном прогоне вырос 126 -> 270 мс — это замер без игроков, под нагрузкой флаг ещё предстоит перемерить. DisableExplicitGC оставлен намеренно: он делает System.gc() no-op, и именно поэтому освобождение сделано флагом, а не вызовом GC. README теперь описывает профиль как 15-25 игроков с пиком 34, а не 30-40. --- zernmc-config/README.md | 61 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/zernmc-config/README.md b/zernmc-config/README.md index da3409920..5b9c8c127 100644 --- a/zernmc-config/README.md +++ b/zernmc-config/README.md @@ -1,8 +1,8 @@ # ZernMC configs -Тюнинг под профиль: **4 vCPU / 8 GB RAM, 30–40 игроков**, боевой сервер с -плагином, который спавнит много `Display`/`Marker` сущностей и частиц -(BlackKnife как референс). +Тюнинг под профиль: **4 vCPU / 8 GB RAM, 15–25 игроков постоянно, пик 34**, +боевой сервер с плагином, который спавнит много `Display`/`Marker` сущностей и +частиц (BlackKnife как референс). Все файлы **парциальные** — Paper/Spigot дополняет недостающие ключи дефолтами, поэтому их можно копировать поверх существующих конфигов. @@ -63,9 +63,60 @@ - Moonrise (чанковая система) — уже многопоточный, отдельный патч не нужен. - `wake-up-inactive.*` — баланс, не перф. +## JVM-флаги кучи (живут в `start.sh`, не в конфигах) + +Эти флаги **не переносятся копированием конфигов** — они в скрипте запуска. +Потеряются при переезде, поэтому дублируются здесь. + +### `-XX:MaxHeapFreeRatio=30 -XX:MinHeapFreeRatio=10` + +Замер на сервере с миром 18 МБ, 0 игроков, после 100 с простоя: + +| | коммит | живые | RSS | +|---|---|---|---| +| без флагов | 630 МБ | 278 МБ | **0.99 ГБ** | +| с флагами | 389 МБ | 274 МБ | **0.69 ГБ** | + +Минус 300 МБ RSS на пустом сервере, без единого вызова GC вручную. + +Почему работает. `G1PeriodicGC` гоняет Mixed/Remark каждые 15 с, но кучу +почти **никогда не сжимает**: G1 сжимает только когда доля свободной памяти +выше `MaxHeapFreeRatio`, а он по умолчанию **70**. При 630 МБ коммита и 278 МБ +живых свободно было 56% — ниже порога, поэтому 350 МБ коммита держались +вечно и в RSS, и в физической памяти. + +Отдельно проверено, что принудительный Full GC сам по себе **не помогает**: +`jcmd GC.run` отработал, живые упали 289 → 248 МБ, а коммит остался на +725 МБ. То есть дело не в отсутствии Full GC, а именно в пороге. + +Паузы при этом не ухудшились — то, что реально ест кадр в рантайме: + +| Pause Young (Mixed) | без флагов | с флагами | +|---|---|---| +| median | 11.0 мс | 9.8 мс | +| p95 | 28.3 мс | 28.1 мс | +| max | 38.1 мс | 28.7 мс | + +Оговорка: concurrent-циклов стало больше (11 → 18), потому что куча меньше и +G1 переполняет её чаще. Каждый такой цикл начинается с STW-паузы, и общий +p95 по всем паузам в одном прогоне вырос (126 → 270 мс). Это замер на сервере +без игроков, один прогон — **под нагрузкой это надо перемерить**, прежде чем +считать флаг безопасным. + +### `-XX:+DisableExplicitGC` +Оставлен намеренно: `System.gc()` отключён, чтобы ни плагин, ни сторонний код +не могли вызвать stop-the-world сборку в неподходящий момент. Именно поэтому +освобождение кучи сделано флагом, а не вызовом GC. + +### Что будет, когда мир вырастет до 2–4 ГБ +Флаг этого не ломает. Он разрешает сжимать кучу, когда свободно больше 30%, +и не мешает расти: при `-Xmx4G` G1 расширится сам, как только живых данных +станет больше. Экономия в 300 МБ в основном на простое и при малом мире. + ## Порядок применения 1. Остановить сервер. 2. Скопировать файлы. 3. `paper-world.yml` положить в **каждый** мир: `world/`, `world_nether/`, `world_the_end/`. -4. Старт → в логе не должно быть `Error occurred while loading` по этим файлам. -5. Проверить: `/paper entity` (tracking ranges), `spark` для TPS/MSPT. \ No newline at end of file +4. Флаги кучи из раздела выше — в `start.sh`, вручную. +5. Старт → в логе не должно быть `Error occurred while loading` по этим файлам. +6. Проверить: `/paper entity` (tracking ranges), `spark` для TPS/MSPT. \ No newline at end of file