TechLead
🧠
Avanzado
12 min lectura

Gestión de memoria y recolección de basura

Comprende fugas de memoria, heap, stack y recolección de basura en JavaScript

Gestión de memoria y recolección de basura

Comprender cómo JavaScript gestiona la memoria es crucial para crear aplicaciones rápidas. Aprende sobre el heap, el stack, la recolección de basura y cómo prevenir fugas de memoria.

1. Memoria de stack vs heap

Memoria de stack:
  • Almacena valores primitivos (números, cadenas, booleanos)
  • Almacena frames de llamadas y variables locales
  • Acceso rápido, limpieza automática cuando la función termina
  • Tamaño limitado (típicamente 1MB)
  • Estructura LIFO (Last In, First Out)
Memoria de heap:
  • Almacena objetos, arrays y funciones
  • Mayor tamaño, acceso más lento que el stack
  • Requiere recolección de basura para liberar memoria
  • Las variables guardan referencias a ubicaciones en el heap
// Stack memory
let x = 10; // Primitive stored on stack
let y = 20;
let sum = x + y; // Result on stack

// Heap memory
let obj = { name: 'John', age: 30 }; // Object on heap
let arr = [1, 2, 3]; // Array on heap
// obj and arr variables (references) are on stack

// Reference example
let a = { value: 5 };
let b = a; // b references same object
b.value = 10;
console.log(a.value); // 10 - both reference same object

2. Recolección de basura

JavaScript usa recolección automática de basura para liberar memoria. Los dos algoritmos principales:

Algoritmo mark-and-sweep (enfoque moderno):

// Root objects (global, currently executing functions)
// are marked as "active"
// GC traverses and marks all reachable objects
// Unreachable objects are swept (deleted)

function createUser() {
  let user = { name: 'John' }; // Created
  return user;
}

let activeUser = createUser(); // user object is reachable
activeUser = null; // Now unreachable, will be garbage collected

// Example: Circular references (handled by mark-and-sweep)
let obj1 = {};
let obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;
obj1 = null;
obj2 = null;
// Both objects are now unreachable and will be GC'd

3. Fugas de memoria comunes

Fuga #1: Variables globales

// ❌ Bad: Accidental global
function leak() {
  user = { name: 'John' }; // No let/const, becomes global
}

// ✅ Good: Use strict mode
'use strict';
function noLeak() {
  let user = { name: 'John' }; // Properly scoped
}

Fuga #2: Event listeners

// ❌ Bad: Listener never removed
function setupListener() {
  const button = document.querySelector('#btn');
  button.addEventListener('click', () => {
    console.log('Clicked');
  });
}

// ✅ Good: Remove listener
function setupListener() {
  const button = document.querySelector('#btn');
  const handler = () => console.log('Clicked');
  button.addEventListener('click', handler);
  
  // Cleanup
  return () => button.removeEventListener('click', handler);
}

// React example
useEffect(() => {
  const handler = () => console.log('Scroll');
  window.addEventListener('scroll', handler);
  
  return () => window.removeEventListener('scroll', handler);
}, []);

Fuga #3: Timers

// ❌ Bad: Timer never cleared
function startTimer() {
  setInterval(() => {
    console.log('Tick');
  }, 1000);
}

// ✅ Good: Clear timer
function startTimer() {
  const timerId = setInterval(() => {
    console.log('Tick');
  }, 1000);
  
  return () => clearInterval(timerId);
}

// React example
useEffect(() => {
  const timerId = setInterval(() => {
    console.log('Tick');
  }, 1000);
  
  return () => clearInterval(timerId);
}, []);

Fuga #4: Closures que mantienen referencias

// ❌ Bad: Closure holds large object
function createHandler() {
  const largeData = new Array(1000000).fill('data');
  
  return function() {
    console.log(largeData[0]); // Holds entire array
  };
}

// ✅ Good: Extract only what you need
function createHandler() {
  const largeData = new Array(1000000).fill('data');
  const firstItem = largeData[0];
  
  return function() {
    console.log(firstItem); // Only holds one item
  };
}

Fuga #5: Elementos DOM desconectados

// ❌ Bad: Keeping reference to removed DOM element
let elements = [];

function addElement() {
  const div = document.createElement('div');
  document.body.appendChild(div);
  elements.push(div); // Keep reference
}

function removeElement() {
  document.body.removeChild(elements[0]);
  // Element removed from DOM but still in memory (elements array)
}

// ✅ Good: Remove all references
function removeElement() {
  const element = elements.shift();
  document.body.removeChild(element);
  // No more references, can be GC'd
}

4. WeakMap y WeakSet

Usa WeakMap y WeakSet para objetos que no quieres impedir que sean recolectados por el GC.

// Regular Map prevents GC
const map = new Map();
let obj = { data: 'important' };
map.set(obj, 'metadata');
obj = null; // Object NOT garbage collected (Map still holds it)

// WeakMap allows GC
const weakMap = new WeakMap();
let obj2 = { data: 'important' };
weakMap.set(obj2, 'metadata');
obj2 = null; // Object CAN be garbage collected

// Practical use: Private data
const privateData = new WeakMap();

class User {
  constructor(name) {
    privateData.set(this, { password: 'secret' });
    this.name = name;
  }
  
  getPassword() {
    return privateData.get(this).password;
  }
}

const user = new User('John');
console.log(user.name); // Accessible
console.log(user.password); // undefined (private)
console.log(user.getPassword()); // 'secret'

5. Perfilado de memoria

// Check memory usage
if (performance.memory) {
  console.log('Heap size:', performance.memory.totalJSHeapSize);
  console.log('Used heap:', performance.memory.usedJSHeapSize);
  console.log('Heap limit:', performance.memory.jsHeapSizeLimit);
}

// Chrome DevTools:
// 1. Open DevTools → Memory tab
// 2. Take heap snapshot
// 3. Compare snapshots to find leaks
// 4. Look for detached DOM nodes
// 5. Check objects in memory

// Force garbage collection (DevTools only)
// Performance → Memory → Collect garbage icon
Buenas prácticas de optimización de memoria:
  • ✓ Limpia siempre event listeners, timers y suscripciones
  • ✓ Evita variables globales
  • ✓ Ten cuidado con closures: no captures datos innecesarios
  • ✓ Elimina referencias a elementos DOM cuando los quites
  • ✓ Usa WeakMap/WeakSet para cachés ligados a objetos
  • ✓ Perfila memoria de forma regular durante el desarrollo
  • ✓ Pon en null objetos grandes cuando termines
  • ✓ Usa object pooling para objetos creados/destruidos frecuentemente