Garbage Collection en Go
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 23 de 107.
A diferencia de lenguajes como C o C++, Go maneja la gestión de memoria automáticamente a través de la recolección de basura (GC). No es necesario liberar la memoria manualmente cuando hayas terminado de usarla. El runtime de Go rastrea qué memoria todavía está en uso y reclama el resto.
Cuando asignas memoria usando new(), make() o creando variables, el recolector de basura de Go monitorea estas asignaciones. Una vez que un valor ya no es alcanzable por ninguna parte de tu programa, el GC libera automáticamente esa memoria.
func createPerson() *Person {
p := &Person{Name: "Alice", Age: 30}
return p // p escapa al heap, el GC lo gestionará
}
func main() {
person := createPerson()
fmt.Println(person.Name)
// Cuando person ya no se utiliza, el GC reclama la memoria
}En este ejemplo, la estructura Person se asigna en el heap porque se devuelve desde la función. El compilador de Go realiza un análisis de escape para determinar si una variable puede permanecer en el stack o debe moverse al heap. El recolector de basura solo gestiona la memoria del heap.
Esta gestión automática de memoria significa que puedes centrarte en construir tu aplicación sin preocuparte por las fugas de memoria debidas a desasignaciones olvidadas. Sin embargo, comprender que el GC existe te ayuda a escribir un código más eficiente al minimizar las asignaciones innecesarias cuando el rendimiento es importante.
Desafío
FácilVamos a construir un gestor de sesiones que demuestre cómo el recolector de basura (garbage collector) de Go maneja la memoria para los objetos que escapan al heap. Crearás sesiones que se asignan dinámicamente y se devuelven desde funciones, permitiendo que el GC gestione su ciclo de vida.
Organizarás tu código en dos archivos:
session.go: Define una estructuraSessioncon los camposID(string),Username(string) yData(un slice de strings). Crea una funciónNewSessionque reciba un ID y un nombre de usuario, asigne una nueva Session en el heap (devolviendo un puntero) e inicialice el slice Data como vacío. También añade un métodoAddDatacon un receptor de puntero que añada un string al slice Data de la sesión, y un métodoSummaryque devuelva un string formateado mostrando los detalles de la sesión.main.go: Lee la información de la sesión desde la entrada, crea una sesión usando tu función constructora, añade algunas entradas de datos a ella y muestra el resumen de la sesión. Dado que la sesión se crea dentro de una función y se devuelve como un puntero, escapa al heap donde el recolector de basura la gestionará.
Se proporcionarán las siguientes entradas:
- Línea 1: Session ID
- Línea 2: Username
- Línea 3: Primera entrada de datos a añadir
- Línea 4: Segunda entrada de datos a añadir
Tu método Summary debe devolver un string con este formato:
Session [ID] for user [Username]
Data entries: [count]
- [entry1]
- [entry2]Por ejemplo, dados sess-001, alice, login y view_dashboard, tu salida debería ser:
Session sess-001 for user alice
Data entries: 2
- login
- view_dashboardLa idea clave aquí es que tu función NewSession crea una Session y devuelve un puntero a ella. Esto hace que la Session "escape" al heap en lugar de quedarse en el stack, lo que la hace elegible para la recolección de basura una vez que no queden referencias a ella. Tu código no necesita liberar manualmente esta memoria; Go lo maneja automáticamente.
Hoja de referencia
Go utiliza la recolección de basura (GC) para la gestión automática de la memoria. No es necesario liberar la memoria manualmente.
El recolector de basura rastrea las asignaciones de memoria y reclama la memoria que ya no es accesible por su programa.
func createPerson() *Person {
p := &Person{Name: "Alice", Age: 30}
return p // p escapes to heap, GC will manage it
}
func main() {
person := createPerson()
fmt.Println(person.Name)
// When person is no longer used, GC reclaims memory
}El compilador de Go realiza un análisis de escape para determinar si una variable permanece en la pila (stack) o se mueve al montón (heap). Las variables que escapan al montón (como los punteros devueltos) son gestionadas por el recolector de basura.
El GC solo gestiona la memoria del montón, no la memoria de la pila. La memoria de la pila se libera automáticamente cuando una función retorna.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leer el ID de sesión
sessionID, _ := reader.ReadString('\n')
sessionID = sessionID[:len(sessionID)-1]
// Leer el nombre de usuario
username, _ := reader.ReadString('\n')
username = username[:len(username)-1]
// Leer la primera entrada de datos
data1, _ := reader.ReadString('\n')
data1 = data1[:len(data1)-1]
// Leer la segunda entrada de datos
data2, _ := reader.ReadString('\n')
data2 = data2[:len(data2)-1]
// TODO: Crear una nueva sesión usando la función NewSession
// La sesión escapará al heap ya que devolvemos un puntero
// TODO: Añadir las entradas de datos a la sesión usando el método AddData
// TODO: Imprimir el resumen de la sesión usando el método Summary
fmt.Println("")
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool