한국어English日本語简体中文繁體中文DeutschไทยTiếng ViệtРусскийPortuguês (Brasil)EspañolBahasa Indonesia

Анатомия игровых лагов › L10 Память

Полная пауза GC на сервере Stop-the-world GC pause

ID причины mem-gc · Основной ответственный Команда разработки · Разработка сервера · Совместно Команда инфраструктуры · Серверная инфраструктура

Открыть карточку в основной версии с иллюстрациями и экспериментами →

Сервер на 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 останавливает запросивший память поток до конца сборки.
Реальные инциденты
Riot Games 2021: Сбой League of Legends EUW на 5 часов: одна второстепенная БД остановила весь сервер

Источники

  1. Garbage-First (G1) Garbage Collector Oracle
    Целевая пауза G1 по умолчанию 200 ms (MaxGCPauseMillis). Если во время сборки кончается память, G1 переходит к Full GC с полной остановкой и сжатием всей кучи
  2. JEP 523: Make G1 the Default Garbage Collector in All Environments OpenJDK
    Раньше при 1 CPU или памяти меньше 1792 MB по умолчанию выбирался Serial GC, начиная с JDK 27 G1 используется по умолчанию везде
  3. JEP 439: Generational ZGC OpenJDK
    Паузы ZGC не дольше 1 ms и не зависят от размера кучи, паузы G1 от единиц ms до нескольких секунд. Если аллокации опережают освобождение памяти, возможна остановка аллокаций (allocation stall)
  4. JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental) OpenJDK
    Паузы Shenandoah примерно одинаковы и при куче 200 MB, и при 200 GB
  5. Background garbage collection .NET
    Фоновый GC применяется только к сборке поколения 2, а сборка поколений 0 и 1 (GC переднего плана) останавливает все управляемые потоки
  6. A Guide to the Go Garbage Collector Go
    GC в Go работает в основном конкурентно, полные остановки короткие. При большом объёме аллокаций горутины берут на себя часть работы GC (assist), и появляются задержки
  7. JEP 271: Unified GC Logging OpenJDK
    Начиная с JDK 9 GC-лог переведён на единое журналирование (-Xlog). -Xlog:gc, как прежний -XX:+PrintGC, пишет по строке на каждый GC
  8. The java Command Oracle
    Таблица замены старых параметров GC-лога на -Xlog: -XX:+PrintGCDetails соответствует -Xlog:gc*
  9. dotnet-counters diagnostic tool .NET
    В .NET 9 и новее метрики публикуются через System.Runtime (dotnet.gc.pause.time и др.), в .NET 8 и ниже через старые EventCounter (% Time in GC since last GC и др.)
  10. runtime package Go
    GODEBUG=gctrace=1: по строке на каждый GC, время по wall clock для каждой фазы, размер кучи в начале и в конце GC и целевой размер кучи

Смотрите также

Тот же слой: L10 Память

Причины с тем же симптомом (Фриз) на других слоях

Карточка в основной версии с иллюстрациями и экспериментами