Сервер на Java или C# останавливает все потоки на время сборки мусора (stop-the-world), и на это время замирает весь сервер.
Почему Куча заполняется, запускается GC → Следствие Все игровые потоки останавливаются на время сборки (чем больше живых данных, тем дольше) → На экране У всех игроков сервера одновременно фриз, затем перемотка
Основной ответственный Команда разработки · Разработка сервера · Совместно Команда инфраструктуры · Серверная инфраструктура
Команда разработки: задачи
Явно задать в параметрах запуска сборщик с короткими паузами (ZGC, Shenandoah, G1 с уменьшенной целевой паузой), сократить аллокации, подобрать размер кучи.
Команда инфраструктуры: задачи
Брать инстансы с запасом памяти под кучу, контейнерам выделять не меньше 2 CPU и около 1,8 GB памяти (при меньших ресурсах JDK 26 и ниже по умолчанию выбирает Serial GC), отслеживать длительность пауз GC.
Цифры для ориентира
Minor GC, который собирает только новые объекты (область Young), занимает от единиц до десятков ms. Full GC по всей куче с несколькими GB живых данных может длиться больше 1 секунды. У ZGC паузы короче 1 ms почти независимо от размера кучи, у Shenandoah паузы тоже короткие, потому что не растут вместе с кучей.
На графике
Всплески с постоянным периодом · время тика сервера, длительность пауз GC
Где смотреть
Включить GC-лог и наложить моменты и длительность пауз на график времени тика сервера. Для Java смотреть строки Pause в логе, который включает параметр запуска -Xlog:gc* (в JDK 8 и ниже -XX:+PrintGCDetails), для .NET метрику пауз GC в dotnet-counters (с .NET 9 dotnet.gc.pause.time, в 8 и ниже % Time in GC since last GC), для Go строки, которые GODEBUG=gctrace=1 пишет на каждый GC
Подтверждает
Всплески времени тика совпадают с паузами GC, длительность пауз близка к длительности всплесков. Во всех зонах и каналах сервера всплеск в один и тот же момент
Опровергает
Если в GC-логе нет длинных пауз, а тик всё равно скачет, причина другая: блокировки, синхронные вызовы, запись на диск. Если скачет только одна зона, это GC скриптового движка (mem-script-gc) или нагрузка в этой зоне
Чем проверить
Инструменты инфраструктуры (игровой код не нужен)
Подробнее
У G1 в Java целевая длительность одной паузы по умолчанию 200 ms, а на 20-тиковом сервере это 4 тика. Если контейнеру дать меньше 2 CPU или меньше примерно 1,8 GB памяти, Java версии JDK 26 и ниже выбирает по умолчанию Serial GC, который собирает мусор в один поток, и паузы становятся намного длиннее. На серверах C# (.NET) обычно включают серверный и фоновый GC, но сборка поколений 0 и 1 (Gen0 и Gen1), где лежат новые объекты, и Full GC со сжатием кучи по-прежнему останавливают все потоки. В Go паузы обычно короче 1 ms, но при большом объёме аллокаций горутине, которая запрашивает память, приходится брать на себя часть работы GC, и тик замедляется. При любом подходе, если аллокации идут быстрее сборки, игровые потоки в итоге останавливаются. G1 переходит к Full GC, а ZGC останавливает запросивший память поток до конца сборки.
Garbage-First (G1) Garbage CollectorOracle Целевая пауза G1 по умолчанию 200 ms (MaxGCPauseMillis). Если во время сборки кончается память, G1 переходит к Full GC с полной остановкой и сжатием всей кучи
JEP 439: Generational ZGCOpenJDK Паузы ZGC не дольше 1 ms и не зависят от размера кучи, паузы G1 от единиц ms до нескольких секунд. Если аллокации опережают освобождение памяти, возможна остановка аллокаций (allocation stall)
Background garbage collection.NET Фоновый GC применяется только к сборке поколения 2, а сборка поколений 0 и 1 (GC переднего плана) останавливает все управляемые потоки
A Guide to the Go Garbage CollectorGo GC в Go работает в основном конкурентно, полные остановки короткие. При большом объёме аллокаций горутины берут на себя часть работы GC (assist), и появляются задержки
JEP 271: Unified GC LoggingOpenJDK Начиная с JDK 9 GC-лог переведён на единое журналирование (-Xlog). -Xlog:gc, как прежний -XX:+PrintGC, пишет по строке на каждый GC
The java CommandOracle Таблица замены старых параметров GC-лога на -Xlog: -XX:+PrintGCDetails соответствует -Xlog:gc*
dotnet-counters diagnostic tool.NET В .NET 9 и новее метрики публикуются через System.Runtime (dotnet.gc.pause.time и др.), в .NET 8 и ниже через старые EventCounter (% Time in GC since last GC и др.)
runtime packageGo GODEBUG=gctrace=1: по строке на каждый GC, время по wall clock для каждой фазы, размер кучи в начале и в конце GC и целевой размер кучи