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 descubre fugas de memoria.

El generador de perfiles del montón muestra la distribución de la memoria realizada por los objetos JavaScript de la página y los nodos del DOM relacionados. Úsalo para tomar instantáneas de 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 de montón:

  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 radio_button_checked Instantánea de montón, luego selecciona una instancia de VM de JavaScript y haz clic en Tomar instantánea.

Un tipo de perfilado y una instancia de VM de JavaScript seleccionados.

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 INSTRUCCIONES DE HEAP.

El tamaño total de los objetos accesibles.

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 instantánea siempre comienza con la recolección de elementos no utilizados.

Es una instantánea de montón de objetos Item dispersos.

Borrar instantáneas

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

Borrar 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:

View 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 realizar 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. Confirma la presencia y la causa de una fuga de memoria inspeccionando el delta en la memoria liberada y el recuento de referencias.
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 qué los mantiene. Úsalo para analizar cortes y profundizar en los objetos a 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 Summary que enumera los 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 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 Tamaño superficial, se muestra la suma de tamaños aplanados 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í mismo. En general, los arrays y las cadenas tienen tamaños superficiales más grandes. Consulta también Tamaños de objetos.
  • Retained Size muestra el tamaño retenido máximo en el mismo conjunto de objetos. El tamaño retenido es el tamaño de memoria que puedes liberar borrando un objeto y haciendo 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 capturas de pantalla de montón por objeto.

Filtros del constructor

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

Para utilizar estos filtros, selecciona una de las siguientes opciones en el menú desplegable que se encuentra en el extremo derecho de la barra de acciones:

  • Todos los objetos: Todos los objetos capturados por la instantánea actual. Establecida 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 las Instantáneas 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.
  • Strings duplicadas: Son valores de strings que se almacenaron varias veces en la memoria.
  • Objetos retenidos por nodos separados: Son objetos que se mantienen activos porque un nodo del DOM desconectado hace referencia a ellos.
  • Objetos retenidos por la consola de Herramientas para desarrolladores: Son objetos que se conservan 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 Summary también agrupa objetos por:

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

Entradas de 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 facilitar el cambio de tamaño. Del mismo modo, las propiedades con nombre en los objetos de 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 poder 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 poder ejecutarse más rápido. Si una función no se ejecutó por un tiempo, es posible que V8 borre sus datos internos.

(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 cadena 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 C++, usa Chrome for Testing y haz lo siguiente:

  1. Abre Herramientas para desarrolladores y activa Configuración Configuración > Experimentos > check_box Mostrar opción para exponer componentes internos en instantáneas de montón.
  2. Abre el panel Memoria, selecciona radio_button_checked Instantánea de montón y activa radio_button_checked Exponer componentes internos (incluye detalles adicionales específicos de la implementación).
  3. Reproduce el problema que provocó que el 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 nuevo objeto 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 cadena. Si una cadena cortada retiene mucha memoria, es posible que el programa haya activado el error 2869 y podría beneficiarse de 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 a la Context en la que se ejecuta, de modo 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 (aún) no se han categorizado de ninguna manera más significativa.

Vista de comparación

La vista Comparison te permite encontrar objetos filtrados comparando varias instantáneas 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 fugas, 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 puede estar causando una fuga.
  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 Comparison (Comparación) 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 agregadas y borradas:

En comparación con la Instantánea 1.

Vista de contención

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

La vista proporciona varios puntos de entrada:

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

La vista Contención.

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. El panel Memory (Memoria) actualiza la sección Retainers cuando seleccionas objetos diferentes en cualquiera de las vistas, excepto Statistics.

La sección Contratos de servicio.

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

Ignorar retenedores

Puedes ocultar los retenedores para descubrir qué otros objetos retienen el seleccionado. Con esta opción, no es necesario que primero quites este retenedor del código y que luego vuelvas a tomar la instantánea del montón.

La opción "Ignorar este retenedor" en el menú desplegable.

Para ocultar un retenedor, haz clic con el botón derecho y selecciona Ignorar este retenedor. Los retenedores ignorados se marcan como ignored en la columna Distance. Para dejar de ignorar todos los retenedores, haz clic en playlist_remove Restaurar los retenedores ignorados en la barra de acciones en la parte superior.

Encuentra un objeto específico

Para encontrar un objeto en el montón recopilado, puedes realizar una búsqueda 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 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 cambio, 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 del montón puede reflejar dependencias bidireccionales entre los objetos nativos del navegador (nodos del DOM y reglas CSS) y objetos JavaScript. Esto ayuda a descubrir fugas que, de otro modo, serían invisibles y se producían debido a subárboles del DOM separados que se habían olvidado.

Las fugas del DOM pueden ser más grandes de lo que crees. Ten en cuenta el siguiente ejemplo. ¿Cuándo se recoge la basura 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 forma recurrente, hasta #tree, por lo que solo cuando leafRef se anula, todo el árbol bajo #tree un candidato para la recolección de elementos no utilizados.

Subárboles del DOM