Analice el siguiente fragmento de código y determine cuántas variables escapan del proceso de recolección de basura:
var contador = 10;
var contador = 20;
var generador = function() {
var pasos = 0;
return function() {
pasos += 1;
console.log(pasos);
}
}();
generador();
Respuesta: 3. Existen dos variables en el ámbito global (contador y generador) y una variable local (pasos). Dado que la variable local es capturada y referenciada por un ámbito interno (clausura), tampoco es elegible para la eliminación.
var contador = 10; // Variable global: persiste en memoria
var contador = 20; // La redeclaración con 'var' se ignora, solo se actualiza el valor
var generador = function() { // Variable global: persiste en memoria
var pasos = 0; // Variable local
return function() {
pasos += 1;
console.log(pasos); // Referenciada por otro ámbito: persiste en memoria
}
}();
generador();
Reglas Fundamentales de Liberación de Variables
- Las variables declaradas en el ámbito global nunca se eliminan automáticamente mientras el contexto global esté activo.
- Las variables locales se destruyen una vez que la función termina su ejecución y su contexto de ejecución es eliminado de la pila de llamadas.
- Cualquier variable que sea referenciada por un ámbito externo o diferente (como ocurre en las clausuras) permanece activa en memoria.
Algoritmos de Recolección de Basura en JavaScript
Marcado y Eliminación (Mark-and-Sweep)
Este es el algoritmo estándar y más utilizado en los motores modernos de JavaScript. Su funcionamiento se basa en el concepto de "accesibilidad" y se ejecuta en tres fases principales:
- El recolector de basura identifica las raíces (roots). En JavaScript, la raíz principal es el objeto global (
windowen navegadores oglobalen entornos como Node.js). - Se inspeccionan todas las raíces y se recorren recursivamente sus referencias. Cualquier objeto que pueda ser alcanzado desde una raíz se marca como "activo" o "vivo".
- Todos los objetos que no fueron marcados como activos se consideran inaccesibles y su memoria es liberada.
Nota importante: Un objeto con "cero referencias" es siempre inaccesible desde la raíz. Sin embargo, un objeto inaccesible desde la raíz no siempre tiene cero referencias (por ejemplo, en estructuras con referencias circulares). Esta es la razón por la que el algoritmo de marcado y eliminación es superior y reemplaza al antiguo conteo de referencias.
Conteo de Referencias (Reference Counting)
Principio de funcionamiento: Este método histórico rastreaba el número de referencias que apuntaban a un objeto en memoria. Cuando el conteo llegaba a cero, la memoria se liberaba. Fue descartado en motores modernos debido a su incapacidad para resolver referencias circulares, lo que provocaba fugas de memoria.
Causas Comunes de Fugas de Memoria y sus Soluciones
A pesar de los mecanismos automáticos de recolección, ciertas prácticas de codificación pueden provocar fugas de memoria (memory leaks):
- Variables globales accidentales:
- Causa: Asignar valores a variables no declaradas crea propiedades en el objeto global, las cuales no se recolectan.
- Solución: Activar el modo estricto (
'use strict';) para lanzar errores en lugar de crear globales implícitos.
- Clausuras (Closures) no liberadas:
- Causa: Las clausuras mantienen vivo el ámbito léxico de su función padre, impidiendo la liberación de sus variables locales.
- Solución: Nullificar las referencias internas cuando ya no sean necesarias o definir los controladores de eventos fuera del ámbito de la clausura.
- Referencias huérfanas al DOM:
- Causa: Guardar nodos del DOM en variables o estructuras de datos después de que el nodo ha sido eliminado del árbol DOM.
- Solución: Establecer las referencias a
nullmanualmente antes o después de eliminar el elemento del DOM.
- Temporizadores y callbacks olvidados:
- Causa: Usar
setIntervalosetTimeoutque capturan referencias a elemnetos o variables, y no se limpian al destruir el componente. - Solución: Utilizar
clearIntervaloclearTimeouten los ciclos de vida de destrucción de los componentes.
- Causa: Usar
- Referencias indirectas a elementos secundarios:
- Causa: Almacenar referencias a elementos hijos en estructuras de datos complejas. Si el padre se elimina del DOM pero la referencia al hijo persiste en memoria, el árbol completo puede permanecer en el heap.
- Solución: Limpiar y nullifiacr las referencias en cachés o mapas de datos cuando los elementos del DOM asociados sean destruidos.