Cómo grabar instantáneas de montón

Meggin Kearney
Meggin Kearney
Sofia Emelianova
Sofia Emelianova

Aprende a registrar instantáneas del montón con Memoria > Perfiles > Instantánea del montón y a encontrar fugas de memoria.

El generador de perfiles de montón muestra la distribución de la memoria según los objetos de JavaScript de tu página y los nodos DOM relacionados. Úsalo para tomar capturas de pantalla de montón de JS, analizar gráficos de memoria, comparar capturas de pantalla 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, haz lo siguiente:

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

Un tipo de generación de perfiles seleccionado y una instancia de VM de JavaScript

Cuando el panel Memoria carga y analiza la instantánea, muestra el tamaño total de los objetos de JavaScript accesibles debajo del título de la instantánea en la sección CAPTURAS DE PANTALLA DEL MONTÓN.

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

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

Es un resumen del montón de objetos Item dispersos.

Borrar instantáneas

Para quitar todas las instantáneas, haz clic en Clear all profiles:

Borra todos los perfiles.

Ver instantáneas

Si quieres inspeccionar las instantáneas desde diferentes perspectivas para distintos fines, selecciona una de las vistas en el menú desplegable ubicado en la parte superior:

Ver Contenido Objetivo
Resumen Objetos agrupados por nombres de constructores. Úsalo para buscar objetos y su uso de memoria según el tipo. Resulta útil para hacer un seguimiento de las fugas 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. Inspecciona el delta en la memoria liberada y el recuento de referencias para confirmar la presencia y la causa de una fuga de memoria.
Contención Contenido del montón Proporciona una mejor vista de la estructura de objetos y ayuda a analizar los objetos a los que se hace referencia en el espacio de nombres global (ventana) para descubrir la razón por la cual no se borran. Úsala para analizar cierres y examinar los objetos en un nivel bajo.
Estadísticas Gráfico circular de la asignación de memoria Consulta los tamaños reales de las partes de la memoria asignadas al código, las cadenas, los arrays de JS, los arrays escritos y los objetos del sistema.

La vista Resumen seleccionada en el menú desplegable de la parte superior.

Vista de resumen

Inicialmente, se abre una instantánea del montón en la vista Resumen que muestra los Constructores 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 los constructores irrelevantes, escribe un nombre que quieras inspeccionar en el filtro de clase en la parte superior de la vista Resumen.

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:

  • Distancia: Muestra la distancia a la raíz con la ruta de nodos más corta y simple.
  • En Shallow size, se 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 un objeto retiene. Por lo general, las matrices y las cadenas tienen tamaños superficiales más grandes. Consulta también Tamaños de objetos.
  • En Tamaño retenido, se muestra el tamaño retenido máximo en 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 sus elementos dependientes ya no sean accesibles. 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 que figura después del carácter @ es el ID único del objeto. Te permite comparar capturas de pantalla de montón por objeto.

Filtros de constructor

La vista Resumen te permite filtrar los constructores en función de casos comunes de uso ineficiente de la memoria.

Para usar estos filtros, selecciona una de las siguientes opciones en el menú desplegable de la derecha de la barra de acción:

  • Todos los objetos: Todos los objetos capturados por la instantánea actual. Se establece de forma predeterminada.
  • Objetos asignados antes de la instantánea 1: Son los objetos que se crearon y permanecieron en la memoria antes de que se tomara la primera instantánea.
  • Objetos asignados entre las instantáneas 1 y 2: Consulta la diferencia en los objetos entre la instantánea más reciente y la anterior. Cada instantánea nueva agrega un incremento de este filtro a la lista desplegable.
  • Cadena duplicada: Son valores de cadena que se almacenaron varias veces en la memoria.
  • Objetos retenidos por nodos separados: Son objetos que se mantienen activos porque un nodo DOM separado hace referencia a ellos.
  • Objetos retenidos por la consola de Herramientas para desarrolladores: Son objetos que se mantienen en la memoria porque se evaluaron o interactuaron con ellos a través de la consola de Herramientas para desarrolladores.

Entradas especiales en el resumen

Además de agrupar por constructores, la vista Resumen también agrupa objetos por los siguientes criterios:

  • Funciones integradas, como Array o Object
  • Elementos HTML agrupados por sus etiquetas, por ejemplo, <div>, <a>, <img> y otros.
  • Las funciones que definiste en tu código
  • Son categorías especiales que no se basan en constructores.

Entradas del constructor

(array)

Esta categoría incluye varios objetos internos similares a un array que no corresponden directamente a los 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 permitir un cambio de tamaño más fácil. Del mismo modo, las propiedades con nombre en los objetos de JavaScript suelen almacenarse en objetos internos secundarios llamados (object properties)[] que también se incluyen 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 para que se ejecute más rápido. Si una función no se ejecuta desde hace un tiempo, es posible que V8 borre los datos internos de esa función.

(concatenated string)

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

En lugar de copiar todos los caracteres de las dos cadenas de origen en una nueva, V8 asigna un objeto pequeño con campos internos llamados first y second, que apuntan a las dos cadenas de origen. Esto le permite a V8 ahorrar 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 los objetos asignados fuera de V8, como los objetos C++ definidos por Blink.

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

  1. Abre DevTools y activa Settings > Experiments > Show option to expose internals in heap snapshots.
  2. Abre el panel Memoria, selecciona Instantánea de montón y activa 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 rastrea clases ocultas (o formas) para que varios objetos con las mismas propiedades en el mismo orden se puedan representar de manera eficiente. Esta categoría contiene esas clases ocultas, llamadas system / Map (no relacionadas con Map de JavaScript), y datos relacionados.

(sliced string)

Cuando V8 necesita tomar una subcadena, como cuando el código JavaScript llama a String.prototype.substring(), es posible que V8 elija asignar un objeto de cadena dividida en lugar de copiar todos los caracteres relevantes de la cadena original. Este objeto nuevo contiene un puntero a la cadena original y describe qué rango de caracteres de la cadena original se debe usar.

Desde la perspectiva del código JavaScript, estas son solo cadenas normales y se comportan como cualquier otra. Si una cadena cortada retiene mucha memoria, es posible que el programa haya activado el problema 2869 y que sea conveniente tomar medidas deliberadas para “aplanar” la cadena cortada.

system / Context

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

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

(system)

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

Vista de comparación

La vista Comparison te permite encontrar objetos filtrados comparando varias capturas de pantalla entre sí. Por ejemplo, realizar una acción e revertirla, como abrir un documento y cerrarlo, no debería dejar objetos adicionales.

Para verificar que una operación determinada no cree filtraciones, haz lo siguiente:

  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, realiza la interacción opuesta y repítela varias veces.
  4. Toma una segunda instantánea del montón y cambia su vista a Comparación, comparándola con la Instantánea 1.

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

Comparación con la instantánea 1.

Vista de contención

La vista Containment es una "vista panorámica" de la estructura de objetos de tu aplicación. Te permite ver dentro de cierres de funciones, observar objetos internos de las VM que constituyen los objetos de JavaScript y comprender cuánta memoria usa tu app 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. Son las raíces de GC que usa el recolector de basura de la VM. Las raíces de GC pueden constar de 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. Son objetos del navegador que se insertan en la máquina virtual de JavaScript para permitir la automatización, por ejemplo, nodos del DOM y reglas de CSS.

La vista Containment.

La sección Retenedores

En la sección Retenedores, en la parte inferior del panel Memoria, se muestran los objetos que apuntan al objeto seleccionado en la vista. El panel Memoria actualiza la sección Retenedores cuando seleccionas objetos diferentes en cualquiera de las vistas, excepto Estadísticas.

La sección Retenciones.

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

Ignora los retenedores

Puedes ocultar los retenedores para saber si algún otro objeto retiene el seleccionado. Con esta opción, no tienes que quitar primero este retenedor del código y, luego, volver a tomar la instantánea del montón.

La opción &quot;Ignorar este retenedor&quot; en el menú desplegable.

Para ocultar un retenedor, haz clic con el botón derecho y selecciona Ignorar este retenedor. Los retenedores omitidos se marcan como ignored en la columna Distancia. Para dejar de ignorar todos los retenedores, haz clic en Restore ignored retainers en la barra de acción de la parte superior.

Cómo encontrar 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.

Nombrar funciones para distinguir cierres

Resulta muy útil nombrar las funciones para que puedas distinguir entre cierres y capturas de pantalla.

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 realizan las siguientes acciones:

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 puede reflejar dependencias bidireccionales entre objetos nativos del navegador (nodos del DOM y reglas de CSS) y objetos de JavaScript. Esto ayuda a descubrir fugas que, de otro modo, serían invisibles y que se producen debido a subárboles del DOM separados que quedan dando vueltas.

Las fugas del DOM pueden ser más grandes de lo que crees. Considera el siguiente ejemplo: ¿Cuándo se recolecta el elemento no utilizado #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, el árbol completo debajo de #tree es un candidato para la GC.

Subárboles del DOM