Qué es un heap snapshot y qué le hace a tu página
El grafo completo de objetos vivos, cómo se toma, qué garantías da la recolección previa, y las tres formas de perfilado de memoria y para qué sirve cada una.
Los paneles de rendimiento miden el tiempo; el de memoria mide algo completamente distinto y con un modelo mental que no se parece. Un heap snapshot no es una métrica ni una serie temporal: es una fotografía del grafo entero de objetos vivos en el momento de tomarla, con cada objeto, cada referencia entre ellos y el camino que los mantiene alcanzables. Es la estructura de datos más informativa que ofrecen estas herramientas y también la que más gente abre una vez, se asusta y no vuelve a abrir.
- Explicar qué contiene un snapshot y qué relación tiene con la recolección de basura.
- Tomar un snapshot correctamente y saber qué le hace a la página.
- Elegir entre las tres formas de perfilado de memoria según la pregunta.
- Interpretar el tamaño total y por qué no coincide con lo que reporta el sistema.
Qué es exactamente
El montón es la región donde viven los objetos de JavaScript. Un snapshot lo recorre entero y produce un grafo dirigido: los nodos son objetos, cadenas, arrays, funciones, nodos del documento y estructuras internas del motor; las aristas son referencias, cada una con su nombre —la propiedad, el índice, la variable capturada— y su tipo.
Tres propiedades de esa estructura explican todo lo que se puede hacer con ella.
Solo contiene objetos vivos. Antes de tomar el snapshot, el motor ejecuta una recolección de basura completa. Todo lo que aparezca en la foto es, por definición, alcanzable desde alguna raíz. Esto es lo que convierte el snapshot en una herramienta de diagnóstico de fugas: si algo que esperabas liberado aparece, es que hay una referencia viva, y el grafo la contiene.
Contiene las raíces. El grafo tiene nodos especiales que son los puntos de partida de la alcanzabilidad: el objeto global, las pilas de ejecución activas, los manejadores registrados, los objetos retenidos desde el propio navegador. Todo lo demás está vivo porque hay un camino desde alguna de ellas.
Contiene los nodos del documento. Los elementos del DOM aparecen como objetos del montón con su propio tipo, lo que permite responder la pregunta más útil de todas: qué elementos siguen en memoria después de haber sido quitados de la página.
Tomar un snapshot fuerza una recolección de basura completa y detiene la ejecución mientras dura. En una aplicación con mucha memoria puede tardar varios segundos y congelar la pestaña. No es un efecto secundario indeseado: es precisamente lo que garantiza que la foto no contenga basura pendiente de recoger, y sin esa garantía toda la técnica de comparación entre snapshots dejaría de funcionar.
Cómo se toma
El panel de memoria se abre por su nombre desde el menú de comandos. Ofrece varios tipos de perfilado y el primero es el snapshot.
El procedimiento que produce snapshots comparables tiene cuatro reglas.
Ventana de incógnito, sin extensiones. Una extensión puede aportar decenas de miles de objetos al grafo y hace ilegible cualquier comparación.
Estado conocido. Antes de tomar el primero, lleva la aplicación a un estado definido y anótalo. Los snapshots solo se comparan si sus estados son comparables.
Sin interacción durante la captura. La página está congelada de todas formas, pero la costumbre evita capturas a medio camino.
Nombres. El panel guarda los snapshots en una lista; renombrarlos con lo que representan es la diferencia entre una sesión ordenada y tres números sin significado.
Además del botón, la recolección se puede forzar de forma independiente desde el propio panel de memoria, y eso es útil para comprobar si algo se libera sin el coste de tomar una foto entera.
Los tres tipos de perfilado
El panel ofrece tres instrumentos con propósitos distintos, y elegir el equivocado hace perder mucho tiempo.
| Instrumento | Qué produce | Pregunta que responde |
|---|---|---|
| Instantánea del montón | El grafo completo en un instante | Qué hay vivo ahora y quién lo retiene |
| Instrumentación de asignaciones en la línea de tiempo | Cuándo se asignó cada cosa y si sigue viva | En qué momento se creó lo que no se libera |
| Muestreo de asignaciones | Un perfil estadístico por pila de llamadas | Qué parte del código asigna más memoria |
La instantánea es la herramienta de diagnóstico de fugas. La línea de tiempo de asignaciones es la que localiza el instante y la acción responsable. El muestreo es la de menor sobrecarga y sirve para sesiones largas donde lo que interesa no es qué se retiene sino qué código genera presión sobre el recolector.
Una diferencia práctica importante: el muestreo tiene muy poca sobrecarga y se puede dejar corriendo minutos, mientras que la instrumentación completa ralentiza notablemente la página. Para una sesión de veinte minutos buscando una degradación lenta, el muestreo es la opción viable.
El tamaño total y por qué no coincide
El panel muestra el tamaño total del montón, y ese número no coincide con lo que el sistema operativo dice que consume el navegador. Las razones son cuatro y conviene conocerlas para no perseguir discrepancias inexistentes.
El montón de JavaScript es solo una parte. La memoria del proceso incluye también las estructuras del documento fuera del montón, las texturas de composición, los descodificadores de imagen, los búferes de red y el propio código del navegador.
Hay memoria externa que el montón referencia pero no contiene. Los datos binarios de un búfer de array, por ejemplo, viven fuera y solo su objeto envoltorio está dentro.
El proceso reserva más de lo que usa. Los asignadores piden memoria al sistema en bloques y no la devuelven inmediatamente al liberar objetos.
Hay varios procesos. Cada pestaña, cada worker y cada marco aislado puede tener el suyo.
Para una medida agregada y comparable de todo lo que consume una página, existe una API específica que suma todas esas partes, con la condición de que el documento esté aislado por origen:
// Medida agregada de memoria de la pagina, si el contexto lo permite
(async () => {
if (!crossOriginIsolated) {
console.warn('Se necesita aislamiento de origen cruzado. Las cabeceras COOP y COEP.');
}
if (typeof performance.measureUserAgentSpecificMemory !== 'function') {
console.warn('API no disponible en este navegador o contexto.');
// Alternativa aproximada, solo en Chrome y con valores cuantizados:
if (performance.memory) {
console.table([{
usadoMB: +(performance.memory.usedJSHeapSize / 1048576).toFixed(1),
totalMB: +(performance.memory.totalJSHeapSize / 1048576).toFixed(1),
limiteMB: +(performance.memory.jsHeapSizeLimit / 1048576).toFixed(1)
}]);
}
return;
}
const r = await performance.measureUserAgentSpecificMemory();
console.log('Total:', (r.bytes / 1048576).toFixed(1), 'MB');
console.table(r.breakdown
.filter(b => b.bytes > 0)
.map(b => ({
mb: +(b.bytes / 1048576).toFixed(2),
tipos: b.types.join(', '),
contexto: b.attribution.map(a => a.url).join(', ') || '(propio)'
}))
.sort((a, b) => b.mb - a.mb));
})();
La propiedad no estándar que se usa como alternativa devuelve valores cuantizados y limitados deliberadamente, para no exponer información precisa que sirviera para atacar otros orígenes. Sirve para ver tendencias, no para medir con precisión.
El error de método que hace que la mayoría de la gente abandone esta herramienta en su primer intento es tomar un snapshot y ponerse a buscar el problema dentro. Es una tarea imposible, y no por falta de habilidad: una aplicación normal tiene entre cientos de miles y varios millones de objetos vivos, todos legítimos, todos con su camino de retención perfectamente razonable. Mirar esa lista buscando “lo que sobra” es como mirar el contenido entero de un disco duro buscando el fichero que no debería estar. No hay ninguna propiedad intrínseca que distinga a un objeto que es una fuga de uno que no lo es: una fuga no es un tipo de objeto, es un objeto que sigue vivo cuando ya no debería, y ese “ya no debería” es información que está en tu cabeza, no en el grafo. De ahí se deriva la única forma de trabajar que funciona, y conviene interiorizarla antes de abrir el panel: el snapshot es un instrumento de comparación, no de inspección. Su valor no está en la foto sino en la diferencia entre dos fotos que deberían ser iguales. Si defines un ciclo de acciones que empieza y termina en el mismo estado lógico, entonces cualquier objeto que exista al final y no al principio es sospechoso por construcción, sin necesidad de juzgar nada. Esa es la técnica de los tres snapshots y es prácticamente el único uso productivo de esta herramienta. El corolario práctico es que la parte difícil del trabajo con memoria no es la herramienta sino diseñar el ciclo: encontrar una secuencia de acciones que represente el uso real, que sea repetible, y sobre todo que vuelva de verdad al estado inicial. Un ciclo que deja algo legítimamente cargado en caché produce diferencias que no son fugas y hace perder horas. Merece la pena invertir diez minutos en pensar el ciclo antes de tomar el primer snapshot; es tiempo que se recupera multiplicado.