Memoria y garbage collection en JavaScript | Nicolás Garzón
JavaScript administra memoria automáticamente. Un valor puede ser recolectado cuando deja de ser alcanzable desde las raíces activas del programa.
Texto
Copiar roots
├─ globales
├─ Call Stack
├─ módulos activos
├─ callbacks registradas
└─ recursos del host
↓ referencias
objetos alcanzablesJavaScript
Copiar function createUser ( ) {
return {
id : "user-1" ,
} ;
}
const user = createUser ( ) ; El motor reserva memoria sin que escribas malloc o free.
Las raíces dependen del runtime, pero incluyen normalmente:
Variables globales accesibles.
Bindings en contextos de ejecución activos.
Módulos cargados.
Callbacks registradas por el host.
Nodos DOM y objetos mantenidos por APIs del navegador.
Desde ellas se recorren referencias hacia otros valores.
JavaScript
Copiar let user = {
profile : {
name : "Nicolás" ,
} ,
} ; Mientras user sea alcanzable, profile también lo es.
JavaScript
Copiar user = null ; Si ninguna otra referencia apunta al objeto anterior, puede quedar elegible para recolección.
JavaScript
Copiar let first = {
name : "Nicolás" ,
} ;
const second = first;
first = null ; El objeto sigue alcanzable mediante second.
Asignar null a una referencia no destruye un objeto compartido.
JavaScript
Copiar function createCycle ( ) {
const first = { } ;
const second = { } ;
first. second = second;
second. first = first;
}
createCycle ( ) ; Aunque los objetos se referencien entre sí, el ciclo puede recolectarse cuando deja de ser alcanzable desde las raíces.
Los recolectores modernos no dependen únicamente de reference counting simple.
Marcar las raíces.
Recorrer referencias.
Marcar valores alcanzables.
Recuperar memoria no marcada.
Los motores reales utilizan algoritmos más complejos: generaciones, incrementalidad, concurrencia, compactación y optimizaciones específicas.
No dependas de una implementación exacta para la lógica del programa.
El Call Stack organiza ejecuciones activas. “Heap” se usa para describir memoria dinámica donde viven estructuras y entornos.
La regla educativa “primitivos en stack y objetos en heap” es demasiado rígida. Los motores pueden:
Inlinear valores.
Representar números de formas distintas.
Escapar o no objetos.
Optimizar asignaciones.
Razonar mediante valores, referencias, scope y tiempo de vida es más confiable.
JavaScript
Copiar function createReader ( ) {
const largeData = loadLargeData ( ) ;
return function readFirst ( ) {
return largeData[ 0 ] ;
} ;
}
const reader = createReader ( ) ; El closure conserva el binding largeData mientras reader siga alcanzable.
No significa que todo el scope se conserve de forma ingenua; el motor puede optimizar bindings no necesarios. Desde el modelo del lenguaje, cualquier dato realmente capturado puede mantener su entorno accesible.
JavaScript
Copiar const cache = new Map ( ) ;
export function getCache ( ) {
return cache;
} El estado de módulo puede vivir durante toda la aplicación. Una cache sin límites no desaparece solo porque una operación terminó.
JavaScript
Copiar window. addEventListener ( "resize" , handler) ; El host conserva handler, que puede conservar su closure y objetos relacionados.
JavaScript
Copiar window. removeEventListener ( "resize" , handler) ; o un AbortSignal permiten terminar esa relación.
JavaScript
Copiar const timeoutId = setTimeout ( handler, 60000 ) ; Mientras el timer esté pendiente, el host necesita conservar la callback.
JavaScript
Copiar clearTimeout ( timeoutId) ; JavaScript
Copiar const element = document. querySelector ( ".panel" ) ;
element. remove ( ) ; El nodo salió del documento, pero la variable todavía lo alcanza. También puede mantenerse mediante listeners, arrays o closures.
JavaScript
Copiar const metadata = new WeakMap ( ) ;
metadata. set ( element, data) ; La entrada no mantiene viva la clave por sí sola. Son útiles para metadatos ligados al tiempo de vida de un objeto.
No puedes enumerarlas porque el momento de recolección no es observable de forma determinista.
Permite solicitar una callback después de la recolección de un valor.
Cerrar archivos de forma crítica.
Guardar datos.
Confirmar transacciones.
Medir el momento de destrucción.
La callback puede tardar o no ejecutarse antes de terminar el proceso.
Un SharedArrayBuffer puede ser alcanzable desde varios agentes. Su tiempo de vida y coordinación no se explican como un objeto local ordinario; requiere protocolos de comunicación y sincronización.
El garbage collector decide cuándo trabajar según asignaciones, presión, heurísticas y estado del runtime.
Crear muchos objetos temporales puede aumentar frecuencia de GC, pero “evitar objetos” sin medición puede empeorar claridad por un beneficio inexistente.
JavaScript
Copiar class LimitedCache {
#entries = new Map ( ) ;
constructor ( limit ) {
this . limit = limit;
}
set ( key, value) {
if ( this . #entries. has ( key) ) {
this . #entries. delete ( key) ;
}
this . #entries. set ( key, value) ;
if ( this . #entries. size > this . limit) {
const oldestKey = this . #entries. keys ( ) . next ( ) . value;
this . #entries. delete ( oldestKey) ;
}
}
get ( key) {
return this . #entries. get ( key) ;
}
} La cache define explícitamente cuánto puede retener.
Pensar que asignar null destruye un objeto inmediatamente.
Creer que los ciclos nunca se recolectan.
Aplicar la regla stack/heap como contrato exacto.
Ignorar estado de módulo y caches.
Retirar un nodo del DOM y asumir que ya no tiene referencias.
Usar FinalizationRegistry para cleanup crítico.
Forzar optimizaciones de asignación sin medir.
Esperar controlar cuándo ocurre el garbage collector.
JavaScript administra memoria automáticamente.
La reachability determina qué puede seguir vivo.
Los alias mantienen objetos alcanzables.
Los ciclos pueden recolectarse.
Closures, listeners, timers y módulos pueden extender tiempos de vida.
WeakMap no mantiene viva su clave por sí sola.
El momento del GC no es un contrato observable.
Diseña límites y cleanup en recursos de larga vida.
¿Por qué un nodo eliminado del DOM puede continuar en memoria?
Respuesta Porque otra referencia, listener, closure o colección todavía puede alcanzarlo aunque ya no pertenezca al árbol del documento.
Fugas de memoria y limpieza de recursos muestra patrones concretos que conservan datos más tiempo del necesario.