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)
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
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.
Garbage-First (G1) Garbage CollectorOracle 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
JEP 439: Generational ZGCOpenJDK 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)
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
A Guide to the Go Garbage CollectorGo 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
JEP 271: Unified GC LoggingOpenJDK 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
The java CommandOracle Tabla de equivalencias entre las antiguas opciones de log del GC y -Xlog: -XX:+PrintGCDetails pasa a ser -Xlog:gc*
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)
runtime packageGo 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