Memoria y recolector de basura en Go: cómo el GC gestiona los recursos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Memoria y recolector de basura en Go: cómo no volverse loco con el GC

\n

¿Alguna vez te has preguntado a dónde va la memoria en tu aplicación Go? Especialmente cuando empieza a consumir gigabytes y solo estás parseando JSON. ¿Te suena familiar? Bienvenido al mundo del recolector de basura (GC) en Go: un mecanismo misterioso que o salva tu proyecto o lo convierte en una plataforma lenta. Hoy analizaremos cómo funciona la memoria en Go, cómo el GC decide que es hora de limpiar y cómo no caer en la trampa de "esperar el STW".

\n

Si llevas al menos un par de meses escribiendo en Go, ya te has enfrentado a esto: pausas repentinas, aumento del consumo de memoria, misteriosos gc pause en los perfiles. Miremos bajo el capó y entendamos cómo gestionar la memoria en Go de forma consciente.

\n\n

Cómo está organizada la memoria en Go: heap y stack

\n

Antes de hablar del recolector de basura, entendamos dónde viven los datos en Go. A diferencia de lenguajes como C, donde tú eres responsable de malloc/free, Go utiliza la gestión automática de memoria. Pero esto no significa que todo se acumule en un solo montón.

\n

En Go hay dos regiones principales de memoria:

\n
    \n
  • Stack (pila) — memoria rápida y gestionada automáticamente para variables locales. Cada goroutine tiene su propio stack (comienza con 2 KB y crece dinámicamente).
  • \n
  • Heap (montón) — memoria lenta pero flexible para datos que "sobreviven" a la llamada de una función.
  • \n
\n

La pregunta principal: ¿qué va al heap y qué se queda en el stack? De esto se encarga el escape analysis: un mecanismo inteligente del compilador de Go. Analiza si una variable "escapa" del ámbito de la función. Si es así, va al heap. Si no, se queda en el stack y el GC ni siquiera se entera de ella.

\n

Ejemplo:

\n
package main\n\nfunc createPointer() *int {\n    x := 42\n    return &x // x "escapa" — va al heap\n}\n\nfunc noEscape() int {\n    x := 42\n    return x // x se queda en el stack\n}\n
\n

En el primer caso, el compilador ve que el puntero a x se devuelve al exterior y coloca la variable en el heap. En el segundo, todo es simple y rápido. Esto es la gestión de memoria en Go a nivel micro.

\n\n

El recolector de basura en Go: cómo funciona

\n

Ahora, hablemos del protagonista: el recolector de basura (GC). Go utiliza un recolector paralelo, tricolor, no incremental con el algoritmo "mark-and-sweep". ¿Suena aterrador? En realidad, todo tiene lógica.

\n

El GC en Go pasa por tres fases:

\n
    \n
  1. Mark Setup (STW) — una pausa corta para activar la write barrier. Normalmente toma de 10 a 30 microsegundos.
  2. \n
  3. Marking (Concurrente) — el trabajo principal: el GC recorre el grafo de objetos, marcando los vivos. Se ejecuta en paralelo con tu código.
  4. \n
  5. Sweep (Concurrente + STW) — limpieza de objetos muertos. Se hace parcialmente en paralelo, pero la sincronización final puede causar una pausa corta.
  6. \n
\n

Una característica clave del GC en Go es que no realiza desfragmentación. Esto significa que después de la recolección, la memoria puede fragmentarse, pero Go utiliza mapas de bits para rastrear bloques libres, lo que hace que la asignación de memoria sea rápida.

\n

Un ejemplo del funcionamiento del GC se puede ver en los logs:

\n
GODEBUG=gctrace=1 go run main.go\n\ngc 1 @0.013s 4%: 0.031+0.42+0.017 ms clock, 0.25+0.10/0.42/0.0+0.13 ms cpu, 4->4->2 MB, 5 MB goal, 8 P\n
\n

Aquí: 0.031+0.42+0.017 ms es STW + Mark + STW. 4->4->2 MB es el tamaño del heap antes, después de mark y después de sweep.

\n\n

Por qué el GC se ralentiza: STW y pausas

\n

El principal enemigo del rendimiento es el STW (Stop The World). Aunque Go intenta minimizar las pausas, estas siguen ocurriendo. Especialmente si:

\n
    \n
  • Tienes muchos punteros (el GC debe recorrer todos los objetos)
  • \n
  • El heap es grande (más objetos = más tiempo de marking)
  • \n
  • Generas mucha basura (high allocation rate)
  • \n

Blogs

Book Recommendations