El Runtime de Go: Planificador, GC y Memoria
Tu código Go se compila a código máquina, pero la ejecución no es a nivel de hardware.
Busca en todas las páginas de la documentación
Tu código Go se compila a código máquina, pero la ejecución no es a nivel de hardware.
Un runtime empaquetado planifica goroutines, gestiona la memoria del heap, ejecuta el recolector de basura y coordina las llamadas al sistema para que miles de tareas ligeras compartan un pequeño pool de hilos de manera eficiente.
Conceptos Básicos del Runtime recopila fragmentos ejecutables para runtime.MemStats y métricas del GC.
Artículos hermanos cubren el marcado de tres colores, Green Tea GC en Go 1.26, análisis de escape, ajustes, crecimiento de pila, patrones de fuga y flujos de trabajo de producción.
pprof.GOMAXPROCS.pprof, límites de memoria de contenedores, análisis de escape del compilador, métricas de observabilidad.Imagina un proceso Go en ejecución como tres subsistemas cooperativos.
La planificación decide qué goroutine se ejecuta en qué hilo del SO.
La asignación entrega bloques de heap cuando los valores no pueden vivir en la pila.
La recolección de basura rastrea punteros vivos y libera memoria de heap inalcanzable.
Las goroutines no son hilos del SO.
Cada goroutine comienza con una pila pequeña (del orden de unos pocos KiB) que crece bajo demanda.
El runtime detiene las goroutines que se bloquean en canales, mutexes o E/S para que el hilo subyacente pueda ejecutar otro trabajo.
El modelo GPM nombra las piezas:
GOMAXPROCS establece cuántos P existen.
Eso limita cuántas goroutines ejecutan bytecode Go en paralelo en los núcleos de CPU, no cuántas goroutines puedes crear.
Los objetos de heap se crean cuando el compilador no puede probar que la vida útil de un valor cabe en un marco de pila.
Devolver un puntero a una variable local, almacenar datos en interfaces o mapas con vida útil desconocida, o capturar variables en cierres que sobreviven al marco comúnmente fuerzan la asignación de heap.
El asignador organiza la memoria en clases de tamaño dentro de páginas (típicamente 8 KiB en el heap de Go).
El recolector de basura es de rastreo: camina por los punteros desde las raíces (globales, pilas, registros) y marca los objetos alcanzables.
Los objetos inalcanzables se barren y su memoria se devuelve al asignador.
El planificador y el GC interactúan constantemente.
Cuando el GC necesita inspeccionar pilas o aplicar una fase de detención del mundo (STW), las goroutines se pausan brevemente.
La mayor parte del trabajo de marcado se ejecuta concurrentemente con tu código a través de barreras de escritura que rastrean las actualizaciones de punteros mientras la fase de marcado procede.
El algoritmo clásico es barrido de marca de tres colores:
blanco = aún no visitado (basura candidata)
gris = visitado, hijos no escaneados completamente
negro = visitado, hijos escaneados completamente
El runtime mantiene el invariante de que ningún objeto negro apunta a un objeto blanco sin un objeto gris en medio.
El ritmo decide cuándo comienza el próximo ciclo de GC.
Por defecto, GOGC=100 significa que el heap puede crecer hasta aproximadamente el 100% de la memoria viva desde la última recolección antes de activar otra.
GOMEMLIMIT (Go 1.19+) añade un límite de memoria suave para que el recolector marque el ritmo de manera más agresiva a medida que RSS se acerca a un límite, lo cual es crítico en pods de Kubernetes con techos de cgroup estrictos.
Go 1.26 hace de Green Tea GC el recolector por defecto.
En lugar de una lista de trabajo por objeto que salta aleatoriamente por el heap, Green Tea agrupa el escaneo por página, mejorando la localidad de la caché de CPU y permitiendo bucles de escaneo vectorizados en hardware x86 moderno.
Opta por no usarlo en tiempo de compilación con GOEXPERIMENT=nogreenteagc si necesitas la ruta de marca heredada para comparación.
// Ilustrativo: inspeccionar el estado del runtime desde el código de la aplicación
import "runtime"
func snapshot() {
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
_ = ms.HeapAlloc // bytes en uso en el heap
_ = ms.NumGC // ciclos de GC completados
_ = ms.PauseTotalNs
}El análisis de escape se ejecuta en tiempo de compilación.
Cuando la dirección de un valor no puede escapar de su función, el compilador asigna en la pila, sin intervención del GC, liberada al retornar.
Cuando escapa, cada asignación se convierte en trabajo para el GC.
Los servicios de producción instrumentan el runtime en lugar de adivinar.
Exporta runtime/metrics o analiza MemStats para HeapAlloc, HeapInuse, StackInuse, NumGoroutine y PauseNs por ciclo.
Empareja perfiles de heap de net/http/pprof con trazas de asignación para encontrar llamadas make intensivas o boxing no intencionado.
El dimensionamiento de contenedores debe tener en cuenta la memoria de pila (recuento de goroutines por pila promedio) más el objetivo de heap impulsado por GOGC y GOMEMLIMIT.
Un servicio con millones de goroutines en espera puede usar RSS sustancial incluso cuando los objetos de heap son modestos.
| Perilla | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
| GC por defecto (Green Tea) | Menor CPU de marca en heaps típicos | Algunos heaps irregulares ven menos beneficio | Servicios Go 1.26+ sin necesidades especiales de GC |
Ajuste de GOGC | Cambia CPU por memoria o viceversa | Un ajuste incorrecto aumenta los picos de latencia | Trabajadores por lotes vs APIs sensibles a la latencia |
GOMEMLIMIT | Respeta los límites de cgroup | Límite suave; OOM aún posible bajo picos | Kubernetes, serverless, hosts compartidos |
runtime.GC() manual | Fuerza la recolección para pruebas | Perjudica la latencia en producción si se abusa | Solo benchmarks, instantáneas de diagnóstico |
sync.Pool | Reutiliza buffers transitorios | Las entradas del pool pueden limpiarse en GC | Buffers de serialización, espacio de trabajo de codificación |
Los finalizadores (runtime.SetFinalizer) ejecutan código arbitrario durante el GC y son fáciles de usar incorrectamente.
Prefiere métodos Close() explícitos y defer para los recursos.
Las pausas STW son cortas en Go moderno pero aún importan para la latencia de cola.
Mide con GODEBUG=gctrace=1 durante pruebas de carga, no solo en desarrollo.
GOMAXPROCS goroutines ejecutan código Go en CPU a la vez; el exceso de goroutines se pone en cola o se detiene.Planificador de goroutines, asignador de memoria, recolector de basura, gestión de pilas, soporte de reflexión, maquinaria de pánico/defer e interfaces con el SO (llamadas al sistema, señales, temporizadores).
Tu binario lo enlaza automáticamente; no hay una instalación separada al estilo JVM.
Los hilos del SO son pesados (pilas de MB, planificación del kernel).
Las goroutines son baratas (pilas de KiB, planificación en espacio de usuario).
Las M ejecutan goroutines; las P distribuyen el trabajo entre núcleos; las operaciones de bloqueo detienen goroutines sin bloquear hilos innecesariamente.
Cuando el crecimiento del heap cruza el disparador de GC derivado de GOGC, el tamaño del heap vivo y opcionalmente GOMEMLIMIT.
También puedes activarlo manualmente con runtime.GC() para diagnóstico.
Un pequeño fragmento de código que el compilador inserta cuando se actualizan punteros durante el marcado concurrente.
Notifica al GC para que no se pierda objetos vivos mientras el heap muta bajo las goroutines en ejecución.
Green Tea se convirtió en el recolector por defecto.
Escanea por página de heap en lugar de objetos individuales en la lista de trabajo, mejorando la localidad y reduciendo la CPU de la fase de marca para muchas cargas de trabajo.
Compila con GOEXPERIMENT=nogreenteagc para usar el algoritmo heredado.
Indirectamente.
Más P significan más trabajadores de marca paralelos durante el GC, pero también más CPU mutadora concurrente compitiendo por la caché y el ancho de banda de memoria.
Ajusta GOMAXPROCS a los límites de CPU del contenedor.
Los datos vivos permanecen asignados.
HeapAlloc después del GC refleja objetos alcanzables más la fragmentación del asignador y los tramos no utilizados que aún no se han devuelto al SO.
HeapIdle y HeapReleased muestran la memoria devuelta al SO con el tiempo.
Las pilas de goroutines no son objetos recolectados por basura, pero aún consumen RSS.
La recursión profunda o millones de goroutines aumentan StackInuse en MemStats independientemente de la presión del heap.
No en compilaciones de producción.
GOGC=off existe para benchmarks especializados pero es inseguro para servicios de larga duración porque el heap crece sin límite.
Las asignaciones de pila omiten el heap por completo.
Cada escape fuerza una asignación que el GC debe rastrear y barrer eventualmente.
Una alta tasa de asignación es el principal impulsor del tiempo de CPU del GC.
process_resident_memory_bytes, go_memstats_heap_alloc_bytes, go_memstats_gc_cpu_fraction, recuento de goroutines y latencia de cola durante las fases de GC.
Alerta sobre crecimiento sostenido, no picos únicos después de un despliegue.
Las compilaciones alojadas en TinyGo y wazero utilizan runtimes diferentes con compensaciones distintas de GC y planificación.
Esta sección se centra en el runtime estándar de cmd/compile para binarios de servidor Linux/macOS/Windows.
MemStats y recuento de goroutinesVersiones de Pila: Esta página fue escrita para Go 1.26.x (Green Tea GC por defecto, go fix modernizers - verificar parche en la compilación), chi (última - verificar en la compilación), gin (última - verificar en la compilación), echo (última - verificar en la compilación), google.golang.org/grpc (última - verificar en la compilación), sigs.k8s.io/controller-runtime (última - verificar en la compilación), kubebuilder (última - verificar en la compilación), tinygo (última - verificar objetivos de placa en la compilación), wazero (última - verificar en la compilación), y golangci-lint (última - verificar conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 16 jul 2026