Saltar al contenido principal

Gestión de Memoria

Cómo un lenguaje gestiona la memoria es una de las decisiones de diseño más fundamentales. Así es como los diferentes lenguajes manejan la asignación, liberación y seguridad de la memoria.

Descripción General del Modelo de Memoria​

// GARBAGE COLLECTED (automatic)
// V8 engine uses generational GC

// Objects are heap-allocated, GC handles cleanup
let obj = { name: "John" };
obj = null; // Eligible for garbage collection

// WeakRef (allows GC to collect)
let target = { data: "important" };
let weakRef = new WeakRef(target);
weakRef.deref(); // target or undefined

// WeakMap / WeakSet (weak references)
const cache = new WeakMap();
cache.set(obj, "cached");
// When obj is GC'd, entry is removed

// FinalizationRegistry (cleanup callback)
const registry = new FinalizationRegistry((value) => {
console.log("Object collected:", value);
});
registry.register(obj, "my object");

// Memory leaks to watch for:
// - Forgotten timers/intervals
// - Closures holding references
// - Detached DOM nodes
// - Global variables

Puntos Clave​

  • GC vs propiedad vs manual -- La mayoría de los lenguajes (JavaScript, Python, Go, Java, C#, Ruby, PHP) usan recolección de basura: asignas memoria y el runtime la reclama cuando los objetos son inalcanzables. Rust usa propiedad en tiempo de compilación: cada valor tiene un propietario y el compilador impone reglas de préstamo. C, C++ y Zig usan memoria manual: llamas malloc/free, new/delete o allocator.alloc()/allocator.free(). El GC simplifica el desarrollo pero agrega latencia y no-determinismo; la propiedad (Rust) da seguridad sin GC; la gestión manual da control total pero exige disciplina. Elige GC para productividad; Rust para seguridad y rendimiento; C/C++/Zig para sistemas, embebidos o cuando necesites asignación predecible.

  • Conteo de referencias -- Swift usa ARC (en tiempo de compilación): el compilador inserta llamadas retain/release, y weak/unowned rompen los ciclos. C++ tiene std::shared_ptr para propiedad compartida: auto sptr = std::make_shared<int>(42); Rust ofrece Rc<T> (un solo hilo) y Arc<T> (seguro entre hilos) para propiedad compartida. PHP y Python usan conteo de referencias como parte del GC (más detección de ciclos). El conteo de referencias proporciona limpieza determinista cuando se libera la última referencia, pero puede generar sobrecarga y ciclos. Usa ARC/shared_ptr/Rc cuando necesites propiedad compartida; prefiere la propiedad de Rust cuando la propiedad única sea suficiente.

  • Garantías de seguridad de memoria -- Rust proporciona seguridad de memoria sin GC mediante propiedad y préstamo: el compilador rechaza uso después de liberar, doble liberación y carreras de datos. Por ejemplo, let s2 = s1; mueve s1, así que s1 ya no es utilizable después. C y C++ no tienen tales garantías; errores como free(ptr); use(ptr) o desbordamientos de buffer son comunes. El modelo manual de Zig es más seguro que C (sin comportamiento indefinido en código seguro, asignadores explícitos) pero no previene el uso después de liberar. Elige Rust cuando la seguridad de memoria sea crítica; usa C/C++ solo cuando necesites control de bajo nivel y aceptes el riesgo; Zig se sitúa entre ellos.

  • Patrones de limpieza de recursos -- RAII (C++), defer (Go, Zig), using (C#) y with (Python) vinculan el tiempo de vida del recurso al ámbito. C++: std::ifstream file("data.txt"); -- se cierra cuando sale del ámbito. Zig: defer file.close();. C no tiene un patrón incorporado; debes emparejar manualmente asignar/liberar y manejar errores. Estos patrones previenen fugas y simplifican rutas de error. Prefiere lenguajes con limpieza estructurada cuando gestiones archivos, conexiones o recursos personalizados.

  • Asignación en heap vs stack -- Los tipos valor (primitivos, structs en C#/Swift/Rust) a menudo viven en el stack; los tipos referencia y asignaciones dinámicas van al heap. Go usa análisis de escape para decidir: return &x fuerza la asignación en heap. Zig requiere un asignador explícito para asignación en heap: allocator.create(i32) y allocator.destroy(ptr). stackalloc y Span<T> de C# permiten asignación en stack para rendimiento. Entender la asignación ayuda con el rendimiento y la predictibilidad. Para rutas críticas, prefiere asignación en stack cuando sea posible; usa Zig o C cuando necesites control explícito sobre la estrategia de asignación.