perf(config): document and justify the heap-shrink flags
Build Delta / Build paperclip jar (push) Successful in 7m3s

Добавлен -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.
This commit is contained in:
SashegDev
2026-10-04 13:55:14 +00:00
parent be3bfc2369
commit 42f48fea43
+56 -5
View File
@@ -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.
4. Флаги кучи из раздела выше — в `start.sh`, вручную.
5. Старт → в логе не должно быть `Error occurred while loading` по этим файлам.
6. Проверить: `/paper entity` (tracking ranges), `spark` для TPS/MSPT.