La línea de tiempo de asignaciones: cuándo se creó lo que no se libera
El instrumento que graba cada asignación con su instante y su pila de llamadas, cómo se leen las barras azules y grises, y cuándo compensa frente a las instantáneas.
Las instantáneas dicen qué hay vivo; no dicen cuándo se creó ni quién lo creó. La instrumentación de asignaciones en la línea de tiempo añade justamente esas dos dimensiones: graba cada asignación con su marca temporal y su pila de llamadas, y colorea de forma distinta lo que sobrevivió. El resultado es un gráfico donde las barras que quedan en pie después de una operación completa son, literalmente, la fuga, y donde un clic sobre cualquiera de ellas da la línea de código que la produjo.
- Grabar una línea de tiempo de asignaciones sobre un ciclo concreto.
- Interpretar las barras y su código de color.
- Saltar de una barra superviviente a la pila de llamadas que la asignó.
- Elegir entre este instrumento, las instantáneas y el muestreo según el caso.
Cómo se graba y qué dibuja
En el panel de memoria, el segundo tipo de perfilado instrumenta las asignaciones. Se pulsa para empezar, se ejecuta el escenario, y se para.
Durante la grabación, el panel dibuja barras verticales a lo largo del tiempo. La altura de cada barra es la cantidad de memoria asignada en ese instante. El color es lo importante:
Azul: sigue viva. Esa asignación no se ha recolectado en el momento de parar la grabación.
Gris: ya se recolectó. Se asignó y se liberó durante la grabación.
Eso convierte la lectura en algo casi trivial: graba un ciclo completo que debería terminar sin dejar rastro, y mira si quedan barras azules. Si el ciclo abre y cierra un panel, todo lo asignado al abrirlo debería estar gris al terminar. Cada barra azul que quede es memoria que el ciclo dejó atrás.
Seleccionando un rango de barras, la tabla inferior muestra los objetos asignados en ese intervalo, y al elegir uno, su pila de llamadas en el momento de la asignación. Esa pila es la información que ninguna instantánea da.
Esta instrumentación tiene una sobrecarga considerable: registrar cada asignación con su pila ralentiza notablemente la ejecución. Eso significa dos cosas. No la uses para medir tiempos, porque los números no valen. Y no la dejes corriendo minutos en una aplicación que asigne mucho, porque el propio registro consume memoria y puede acabar colgando la pestaña. Es un instrumento de ráfagas cortas y dirigidas.
El procedimiento
Uno: lleva la aplicación al estado inicial y espera a que se calme. Cualquier trabajo de arranque en curso llenará la grabación de ruido.
Dos: empieza a grabar.
Tres: ejecuta el ciclo dos o tres veces, con pausas visibles entre ellas. Las pausas ayudan a identificar visualmente dónde empieza y acaba cada ciclo en el gráfico.
Cuatro: espera unos segundos sin tocar nada antes de parar. Ese margen permite que el recolector actúe y que las asignaciones temporales pasen a gris. Sin él, todo parece vivo.
Cinco: para y mira el patrón. Lo que buscas es un patrón repetido: un grupo de barras por ciclo, con la misma forma. Si en cada grupo queda la misma barra azul en la misma posición relativa, esa es la fuga y es reproducible.
Seis: selecciona la barra azul superviviente y lee la pila de llamadas del objeto que contiene.
La pila de llamadas es lo que cierra el diagnóstico mucho más rápido que la cadena de retención, y por un motivo importante: la cadena de retención dice quién lo retiene ahora; la pila de asignación dice quién lo creó. Para muchos bugs, saber quién lo creó es suficiente porque el sitio donde se crea es también el sitio donde debería registrarse su limpieza.
Los tres instrumentos comparados
| Situación | Instrumento correcto | Por qué |
|---|---|---|
| Sé qué objeto sospecho y quiero saber quién lo retiene | Instantáneas | La cadena de retención solo está ahí |
| Sé que algo fuga en un ciclo corto y no sé qué | Línea de tiempo de asignaciones | El color hace evidente lo que sobrevive |
| La memoria crece durante minutos y no sé por dónde empezar | Muestreo de asignaciones | Es el único con sobrecarga baja para sesiones largas |
| Quiero saber qué parte del código asigna más | Muestreo de asignaciones | Agrega por pila de llamadas |
| Quiero comparar dos estados separados en el tiempo | Instantáneas | Es el único que compara estados |
La combinación que funciona en la práctica es muestreo para orientarse, línea de tiempo para localizar, instantáneas para confirmar y entender la retención. Empezar por las instantáneas cuando no se tiene ninguna hipótesis es el camino más lento de los tres.
Reducir el ruido de asignación
Un problema real de este instrumento es que muchas aplicaciones asignan constantemente por motivos legítimos —cada evento, cada render, cada respuesta— y el gráfico queda tan denso que no se distingue nada. Tres formas de reducirlo.
Aislar el escenario. Si el ciclo se puede reproducir en una página mínima con solo el componente sospechoso, la grabación queda limpia. Cuesta media hora montarlo y ahorra la sesión entera.
Parar todo lo periódico. Sondeos, animaciones, actualizaciones automáticas. Este fragmento los desactiva temporalmente:
// Congela toda la actividad periodica para que la grabacion quede limpia
(() => {
const intervalosOriginales = [];
// Barrido de identificadores: cancela intervalos activos
const maximo = setInterval(() => {}, 100000);
for (let i = 1; i <= maximo; i++) clearInterval(i);
clearInterval(maximo);
console.log('Intervalos cancelados hasta el identificador', maximo);
// Bloquea la creacion de nuevos intervalos durante la sesion
const originalSetInterval = window.setInterval;
window.setInterval = function (fn, ms, ...resto) {
console.warn('Intervalo bloqueado durante la sesion de memoria:', ms, 'ms');
return 0;
};
// Pausa las animaciones en curso
document.getAnimations().forEach(a => a.pause());
window.restaurarActividad = () => {
window.setInterval = originalSetInterval;
document.getAnimations().forEach(a => a.play());
console.log('Actividad restaurada. Recarga si algo se comporta raro.');
};
console.log('Actividad periodica congelada. Ejecuta restaurarActividad() al terminar.');
})();
Es una intervención agresiva y hay que usarla con conciencia: cancelar intervalos por barrido de identificadores afecta a todo, incluido código de terceros y del propio framework. Por eso conviene tener a mano la función de restauración y, en caso de duda, recargar al terminar.
Grabar un intervalo muy corto. Si el ciclo dura doscientos milisegundos, grabar tres segundos incluye quince veces más ruido que señal. Menos es más.
Hay una distinción conceptual que conviene tener muy clara porque las dos informaciones se parecen y responden a cosas opuestas. El camino de retención responde a “por qué sigue vivo”. Es una propiedad del presente: describe el estado actual del grafo, quién apunta a quién ahora mismo. La pila de asignación responde a “de dónde salió”. Es una propiedad del pasado: describe el instante de creación, que puede haber ocurrido en un contexto completamente distinto del actual. Para muchos bugs las dos convergen y da igual cuál mires; para los interesantes, divergen mucho, y saber cuál necesitas ahorra una tarde. El caso donde más divergen es el que más veces aparece en aplicaciones con framework: un objeto se crea dentro de un componente durante un render perfectamente normal, y se queda vivo porque un sistema completamente ajeno —un almacén global, un bus de eventos, una caché de peticiones— lo capturó. La pila de asignación te lleva al componente, que es inocente: ese objeto tenía que crearse ahí. La cadena de retención te lleva al almacén, que es el culpable. Si vas con la pila, acabas reescribiendo un componente que estaba bien. El caso simétrico también existe: un objeto retenido por un cierre de una función que se llama desde cien sitios; la cadena de retención te deja en el cierre y no te dice cuál de las cien llamadas lo creó, mientras que la pila de asignación te da la ruta exacta. La regla práctica que resuelve la elección es esta: si el problema es que algo no se limpia, la respuesta está en la retención; si el problema es que se crea algo que no debería crearse, o se crea demasiadas veces, la respuesta está en la asignación. Y hay una tercera categoría, la más traicionera, en la que las dos son necesarias a la vez: cuando el objeto se crea correctamente, se registra correctamente en un sitio legítimo, y lo que falla es que el registro sobrevive al objeto por un desajuste en el orden de las limpiezas. Ahí hace falta ver la asignación para saber quién lo metió y la retención para saber quién no lo sacó, y solo con las dos juntas la secuencia se entiende.