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

Libro blanco del lag en juegos › L10 Memoria

Pausa stop-the-world del GC en el servidor Stop-the-world GC pause

ID de la causa mem-gc · Responsable principal Desarrollo de servidor (Equipo de desarrollo) · También Infraestructura de servidores (Equipo de infraestructura)

Abrir la ficha interactiva con gráficos y simulaciones →

Un servidor en Java o C# detiene todos sus hilos para recolectar la basura (stop-the-world), y mientras tanto todo el servidor queda detenido.

Por qué El heap se llena y arranca el GC → Efecto Se detienen todos los hilos del juego mientras se recolecta (cuantos más datos vivos, más tarda) → En pantalla Congelamiento simultáneo en todo el servidor y después cámara rápida

Síntomas
Congelamiento, Cámara rápida
Factores
Detención
A quién afecta
Todo el servidor
Cuándo
A intervalos regulares, Cuanto más tiempo lleva encendido
Responsable
Responsable principal Desarrollo de servidor (Equipo de desarrollo) · También Infraestructura de servidores (Equipo de infraestructura)
Tareas (Equipo de desarrollo)
Elegir de forma explícita en las opciones de arranque un GC de pausas cortas (ZGC, Shenandoah o G1 con un objetivo de pausa más bajo), reducir las asignaciones, ajustar el tamaño del heap.
Tareas (Equipo de infraestructura)
Elegir instancias con memoria suficiente para un heap holgado, asignar a los contenedores al menos 2 CPU y unos 1.8 GB de memoria (con menos, JDK 26 y anteriores eligen Serial GC por defecto), monitorear el tiempo de pausa del GC.
Cifras de referencia
Un Minor GC, que solo recolecta los objetos nuevos (generación joven), tarda de unos pocos a decenas de ms. Un Full GC, que recolecta todo un heap con varios GB de datos vivos, puede superar 1 segundo. ZGC se queda por debajo de 1 ms casi sin importar el tamaño del heap, y Shenandoah también tiene pausas cortas porque no crecen con el tamaño del heap.
En el gráfico
Picos periódicos · Tiempo de tick del servidor, tiempo de pausa del GC
Dónde mirar
Activar el log del GC y superponer la hora y la duración de cada pausa al gráfico del tiempo de tick del servidor. En Java, las líneas Pause de la opción de arranque -Xlog:gc* (-XX:+PrintGCDetails en JDK 8 y anteriores); en .NET, la métrica de pausa del GC de dotnet-counters (dotnet.gc.pause.time desde .NET 9; % Time in GC since last GC en .NET 8 y anteriores); en Go, la línea que GODEBUG=gctrace=1 escribe en cada GC
Se confirma si
Los picos de tick coinciden con las pausas del GC y duran más o menos lo mismo que ellas. Todas las zonas y canales del servidor tienen el pico en el mismo instante
Se descarta si
Si hay picos de tick sin pausas largas en el log del GC, apunta a otra causa (locks, llamadas síncronas, escrituras en disco). Si el pico es en una sola zona, más probable: GC del motor de scripts (mem-script-gc) o carga de esa zona
Se verifica con
Con herramientas de infraestructura (no hace falta código del juego)
Para saber más
En Java, G1 tiene por defecto un objetivo de 200 ms por pausa, lo que equivale a 4 ticks en un servidor de 20 ticks. Si un contenedor recibe menos de 2 CPU o menos de unos 1.8 GB de memoria, Java (JDK 26 y anteriores) elige como GC por defecto Serial GC, que recolecta con un solo hilo, y las pausas se alargan mucho. Los servidores en C# (.NET) suelen activar el GC de servidor y el GC en segundo plano, pero las recolecciones de las generaciones 0 y 1 (Gen0 y Gen1), donde están los objetos nuevos, y el Full GC con compactación siguen deteniendo todos los hilos. En Go las pausas suelen durar menos de 1 ms, pero si hay muchas asignaciones, quien pide memoria tiene que asumir parte del trabajo del GC y el tick se ralentiza. Con cualquier GC, si se asigna más rápido de lo que se recolecta, al final el hilo del juego se detiene: G1 pasa a un Full GC y ZGC detiene el hilo que pidió memoria hasta que termina la recolección.
Casos reales
Riot Games 2021: Caída de 5 horas en League of Legends EUW: una BD auxiliar detuvo todo el servidor

Fuentes

  1. Garbage-First (G1) Garbage Collector Oracle
    Objetivo de pausa de G1 de 200 ms por defecto (MaxGCPauseMillis); si la memoria se agota durante la recolección, pasa a un Full GC que detiene y compacta todo el heap
  2. JEP 523: Make G1 the Default Garbage Collector in All Environments OpenJDK
    Hasta ahora, con 1 CPU o menos de 1,792 MB de memoria se elegía Serial GC por defecto; desde JDK 27, G1 es el predeterminado en cualquier entorno
  3. JEP 439: Generational ZGC OpenJDK
    Pausas de ZGC de 1 ms o menos e independientes del tamaño del heap; pausas de G1 de varios ms a varios segundos. Si se asigna más rápido de lo que se libera, hay riesgo de detenciones por asignación (allocation stall)
  4. JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental) OpenJDK
    Las pausas de Shenandoah duran más o menos lo mismo con un heap de 200 MB que con uno de 200 GB
  5. Background garbage collection .NET
    El GC en segundo plano solo se aplica a las recolecciones de la generación 2; las de las generaciones 0 y 1 (GC en primer plano) detienen todos los hilos administrados
  6. A Guide to the Go Garbage Collector Go
    El GC de Go hace la mayor parte de su trabajo de forma concurrente y solo tiene pausas totales breves; si hay muchas asignaciones, las goroutines asumen parte del trabajo del GC (assist) y eso genera latencia
  7. JEP 271: Unified GC Logging OpenJDK
    Desde JDK 9, el log del GC se reimplementó con el logging unificado (-Xlog); -Xlog:gc escribe una línea por GC, como el antiguo -XX:+PrintGC
  8. The java Command Oracle
    Tabla de equivalencias entre las antiguas opciones de log del GC y -Xlog: -XX:+PrintGCDetails pasa a ser -Xlog:gc*
  9. dotnet-counters diagnostic tool .NET
    Desde .NET 9 se muestra con los medidores de System.Runtime (dotnet.gc.pause.time, entre otros); en .NET 8 y anteriores, con los antiguos EventCounter (% Time in GC since last GC, entre otros)
  10. runtime package Go
    GODEBUG=gctrace=1: una línea por GC con el tiempo de reloj de pared de cada fase, el tamaño del heap al inicio y al final del GC y el heap objetivo

Ver también

Misma capa: L10 Memoria

Causas de otras capas con el mismo síntoma (Congelamiento)

Ver la ficha interactiva con gráficos y simulaciones