Cómo grabar instantáneas de montón

Meggin Kearney
Meggin Kearney
Sofia Emelianova
Sofia Emelianova

Aprende a registrar instantáneas de montón con Memory > Profiles > Heap snapshot y encontrar fugas de memoria.

El Generador de perfiles de montón muestra la distribución de la memoria a través de los objetos de JavaScript de tu página y los nodos del DOM relacionados. Úsalo para tomar instantáneas del montón de JS, analizar gráficos de memoria, comparar instantáneas y encontrar fugas de memoria. Para obtener más información, consulta Árbol de retención de objetos.

Tomar una instantánea

Para tomar una instantánea del montón:

  1. En la página de la que quieras generar un perfil, abre Herramientas para desarrolladores y navega al panel Memoria.
  2. Selecciona el tipo de perfil radio_button_checked de instantánea del montón, selecciona una instancia de VM de JavaScript y haz clic en Take snapshot.

Una instancia de VM de JavaScript y un tipo de perfilado seleccionado.

Cuando el panel Memory carga y analiza la instantánea, muestra el tamaño total de los objetos JavaScript accesibles debajo del título de la instantánea en la sección HEAP SNAPSHOTS.

El tamaño total de los objetos que se pueden alcanzar.

Las instantáneas muestran solo los objetos del gráfico de la memoria a los que se puede acceder desde el objeto global. La toma de una instantánea siempre comienza con la recolección de elementos no utilizados.

Instantánea del montón de objetos Item dispersos.

Borrar instantáneas

Para quitar todas las instantáneas, haz clic en bloquear Borrar todos los perfiles:

Borrar todos los perfiles.

Ver instantáneas

Si quieres inspeccionar las instantáneas desde diferentes perspectivas para diferentes propósitos, selecciona una de las vistas del menú desplegable en la parte superior:

Ver Contenido Objetivo
Resumen Objetos agrupados por nombres de constructor Úsalo para encontrar objetos y su uso de memoria en función del tipo. Útil para realizar un seguimiento de las filtraciones del DOM.
Comparación Diferencias entre dos instantáneas Úsala para comparar dos (o más) instantáneas, antes y después de una operación. Para confirmar la presencia y la causa de una fuga de memoria, inspecciona el delta en la memoria libre y el recuento de referencias.
Contención Contenido del montón Proporciona una mejor vista de la estructura de los objetos y ayuda a analizar los objetos a los que se hace referencia en el espacio de nombres global (ventana) para encontrar lo que los mantiene. Úsalo para analizar cortes y revisar tus objetos en un nivel bajo.
Estadísticas Gráfico circular de la asignación de memoria Observa los tamaños reales de las partes de la memoria asignadas a código, cadenas, arrays JS, arrays escritos y objetos del sistema.

La vista Resumen (Summary) seleccionada en el menú desplegable ubicado en la parte superior

Vista de resumen

Inicialmente, se abrirá una instantánea del montón en la vista Summary que muestra Constructors en una columna. Puedes expandir los constructores para ver los objetos de los que crearon instancias.

La vista Summary con un constructor expandido.

Para filtrar constructores irrelevantes, escribe el nombre que quieras inspeccionar en el filtro Class en la parte superior de la vista Summary.

Los números junto a los nombres de los constructores indican la cantidad total de objetos creados con el constructor. La vista Resumen también muestra las siguientes columnas:

  • La Distancia muestra la distancia a la raíz con la ruta de acceso de nodos más corta y simple.
  • Shallow size muestra la suma de tamaños superficiales de todos los objetos creados por un constructor determinado. El tamaño superficial es el tamaño de la memoria que retiene un objeto en sí. En general, los arrays y las cadenas tienen tamaños superficiales más grandes. Consulta también Tamaños de objetos.
  • El tamaño retenido muestra el tamaño retenido máximo entre el mismo conjunto de objetos. El tamaño retenido es el tamaño de la memoria que puedes liberar si borras un objeto y haces que ya no se pueda acceder a sus dependientes. Consulta también Tamaños de objetos.

Cuando expandes un constructor, la vista Summary muestra todas sus instancias. Cada instancia obtiene un desglose de sus tamaños superficiales y retenidos en las columnas correspondientes. El número después del carácter @ es el ID único del objeto. Te permite comparar instantáneas de montón por objeto.

Entradas especiales en Resumen

Además de agrupar por constructores, la vista Summary también agrupa objetos de la siguiente manera:

  • Funciones integradas, como Array o Object
  • Funciones que definiste en tu código.
  • Categorías especiales que no se basan en constructores.

Entradas de constructor

(array)

Esta categoría incluye varios objetos internos similares a arreglos que no se corresponden de forma directa con objetos visibles en JavaScript.

Por ejemplo, el contenido de los objetos Array de JavaScript se almacena en un objeto interno secundario llamado (object elements)[] para facilitar el cambio de tamaño. Del mismo modo, las propiedades con nombre en los objetos JavaScript a menudo se almacenan en objetos internos secundarios llamados (object properties)[] que también se enumeran en la categoría (array).

(compiled code)

Esta categoría incluye datos internos que V8 necesita para ejecutar funciones definidas por JavaScript o WebAssembly. Cada función se puede representar de varias maneras, desde pequeñas y lentas hasta grandes y rápidas.

V8 administra automáticamente el uso de memoria en esta categoría. Si una función se ejecuta muchas veces, V8 usa más memoria para esa función a fin de que pueda ejecutarse más rápido. Si una función no se ha ejecutado por un tiempo, V8 puede borrar los datos internos de esa función.

(concatenated string)

Cuando V8 concatena dos strings, como con el operador + de JavaScript, puede elegir representar el resultado internamente como una "cadena concatenada", también conocida como estructura de datos Rope.

En lugar de copiar todos los caracteres de las dos strings de origen en una nueva string, V8 asigna un objeto pequeño con campos internos llamados first y second, que apuntan a las dos strings de origen. Esto permite que V8 ahorre tiempo y memoria. Desde la perspectiva del código JavaScript, estas son solo cadenas normales y se comportan como cualquier otra cadena.

InternalNode

Esta categoría representa objetos asignados fuera de V8, como objetos C++ definidos por Blink.

Para ver los nombres de clase C++, usa Chrome for Testing y haz lo siguiente:

  1. Abre Herramientas para desarrolladores y activa settings Configuración > Experimentos > check_box Mostrar opción para exponer componentes internos en instantáneas de montón.
  2. Abre el panel Memory, selecciona radio_button_checked Heap snapshot y activa check_box Exponer componentes internos (incluye detalles adicionales específicos de la implementación).
  3. Reproduce el problema que causó que InternalNode retuviera mucha memoria.
  4. Toma una instantánea del montón. En esta instantánea, los objetos tienen nombres de clase C++ en lugar de InternalNode.
(object shape)

Como se describe en Propiedades rápidas de V8, V8 realiza un seguimiento de las clases ocultas (o formas) para que se puedan representar de manera eficiente varios objetos con las mismas propiedades en el mismo orden. Esta categoría contiene esas clases ocultas, llamadas system / Map (no relacionadas con Map de JavaScript) y los datos relacionados.

(sliced string)

Cuando V8 debe tener una subcadena, como cuando el código JavaScript llama a String.prototype.substring(), V8 puede asignar un objeto de cadena desglosada en lugar de copiar todos los caracteres relevantes de la cadena original. Este objeto nuevo contiene un puntero a la cadena original y describe el rango de caracteres de la cadena original que se usará.

Desde la perspectiva del código JavaScript, estas son solo cadenas normales y se comportan como cualquier otra cadena. Si una string fragmentada retiene mucha memoria, es posible que el programa haya activado el error 2869 y que se beneficie si toma medidas deliberadas para “compactar” la string fragmentada.

system / Context

Los objetos internos de tipo system / Context contienen variables locales de un cierre, es decir, un alcance de JavaScript al que puede acceder una función anidada.

Cada instancia de función contiene un puntero interno hacia Context en el que se ejecuta para que pueda acceder a esas variables. Si bien los objetos Context no son visibles directamente desde JavaScript, tienes el control directo sobre ellos.

(system)

Esta categoría contiene varios objetos internos que no se han categorizado (aún) de manera más significativa.

Vista de comparación

En la vista Comparación, puedes encontrar objetos filtrados comparando varias instantáneas entre sí. Por ejemplo, realizar una acción y revertirla, como abrir un documento y cerrarlo, no debería dejar objetos adicionales atrás.

Sigue estos pasos para verificar que una operación determinada no cree fugas:

  1. Toma una instantánea del montón antes de realizar una operación.
  2. Realiza una operación. Es decir, interactúa con una página de alguna manera que creas que podría estar causando una filtración.
  3. Realiza una operación inversa. Es decir, hacer la interacción opuesta y repetirla varias veces.
  4. Toma una segunda instantánea de montón y cambia su vista a Comparison, y la compara con la Snapshot 1.

En la vista Comparison, se muestra la diferencia entre dos instantáneas. Cuando se expande una entrada de total, se muestran las instancias de objetos agregados y borrados:

En comparación con la Instantánea 1.

Vista de contención

La vista Contención es una vista general de la estructura de objetos de tu aplicación. Te permite dar un vistazo a los cierres de funciones, observar los objetos internos de la VM que conforman tus objetos de JavaScript y comprender cuánta memoria usa tu aplicación en un nivel muy bajo.

La vista proporciona varios puntos de entrada:

  • Objetos DOMWindow. Objetos globales para el código JavaScript.
  • Raíces de GC. Raíces de GC utilizadas por el recolector de elementos no utilizados de la VM. Las raíces de GC pueden consistir en mapas de objetos, tablas de símbolos, pilas de subprocesos de VM, cachés de compilación, alcances de controladores y controladores globales integrados.
  • Objetos nativos. Objetos del navegador "enviados" dentro de la máquina virtual de JavaScript para permitir la automatización, por ejemplo, nodos del DOM y reglas CSS.

La vista Containment.

La sección Contratos de servicio

En la sección Retainers, en la parte inferior del panel Memory, se muestran objetos que apuntan al objeto seleccionado en la vista.

La sección Contratos de servicio.

En este ejemplo, la propiedad x de una instancia Item retiene la cadena seleccionada.

Buscar un objeto específico

Para encontrar un objeto en el montón recopilado, puedes buscarlo con Ctrl + F y, luego, ingresar el ID del objeto.

Nombra las funciones para distinguir los cierres

Es muy útil nombrar las funciones para poder distinguir cierres en la instantánea.

Por ejemplo, el siguiente código no usa funciones con nombre:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function() { // this is NOT a named function
    return largeStr;
  };

  return lC;
}

En este ejemplo, se hace lo siguiente:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function lC() { // this IS a named function
    return largeStr;
  };

  return lC;
}

Función con nombre en un cierre.

Descubre fugas del DOM

El generador de perfiles de montón tiene la capacidad de reflejar dependencias bidireccionales entre objetos nativos del navegador (nodos del DOM y reglas de CSS) y los objetos de JavaScript. Esto ayuda a descubrir fugas que, de otra forma, serían invisibles debido a subárboles del DOM separados que flotan alrededor.

Las fugas del DOM pueden ser más grandes de lo que crees. Ten en cuenta el siguiente ejemplo. ¿Cuándo se recolecta el elemento no utilizado de #tree?

  var select = document.querySelector;
  var treeRef = select("#tree");
  var leafRef = select("#leaf");
  var body = select("body");

  body.removeChild(treeRef);

  //#tree can't be GC yet due to treeRef
  treeRef = null;

  //#tree can't be GC yet due to indirect
  //reference from leafRef

  leafRef = null;
  //#NOW #tree can be garbage collected

#leaf mantiene una referencia a su elemento superior (parentNode) y de manera recursiva hasta #tree, por lo que solo cuando se anula leafRef, todo el árbol debajo de #tree es candidato para la recolección de elementos no utilizados.

Subárboles del DOM