Cómo solucionar problemas de memoria

Kayce Basques
Kayce Basques

Aprende a usar Chrome y DevTools para encontrar problemas de memoria que afecten el rendimiento de la página, incluidos fugas o aumentos de memoria y recolecciones frecuentes de elementos no usados.

Resumen

  • Descubre el volumen de memoria que consume tu página con el Administrador de tareas de Chrome.
  • Visualiza el uso de la memoria en el tiempo con grabaciones de líneas de tiempo.
  • Identifica árboles separados del DOM (una causa común de las fugas de memoria) con capturas de pantalla de montón.
  • Averigua el momento en que se asigna memoria nueva a tu montón JS con grabaciones de líneas de tiempo de asignación.
  • Identifica los elementos desconectados que retiene la referencia de JavaScript.

Descripción general

Según el espíritu del modelo de rendimiento RAIL, el enfoque de los esfuerzos de rendimiento deben ser los usuarios.

Muchos problemas de memoria son importantes porque, a menudo, los usuarios pueden percibirlos. Los usuarios pueden percibir problemas de memoria de las siguientes maneras:

  • El rendimiento de una página empeora progresivamente con el tiempo. Es posible que esto sea un síntoma de una fuga de memoria. Una fuga de memoria se produce cuando, por un error en la página, esta última usa progresivamente más y más memoria con el tiempo.
  • El rendimiento de una página exhibe deficiencias constantes. Esto puede ser un síntoma de un aumento de memoria. Un aumento de memoria se produce cuando una página usa más memoria que la necesaria para lograr una velocidad óptima.
  • El rendimiento de una página se retrasa o parece pausarse con frecuencia. Es posible que esto sea un síntoma de recolecciones frecuentes de elementos no usados. La recolección de elementos no usados ocurre cuando el navegador reclama memoria. El navegador determina el momento en que esto sucede. Durante la recolección, la ejecución de todas las secuencias de comandos se pausa. Por lo tanto, si el navegador realiza muchas recolecciones de elementos no usados, se producirán muchas pausas en la ejecución de las secuencias de comandos.

Aumento de memoria: ¿cuánto es "demasiado"?

Una fuga de memoria es fácil de definir. Si un sitio usa cada vez más memoria, significa que existe una fuga. Sin embargo, un aumento de memoria es un poco más difícil de precisar. ¿Qué califica como “usar demasiada memoria”?

No se aplican números estrictos a esto, debido a que los distintos dispositivos y navegadores tienen capacidades diferentes. La misma página que se ejecuta con fluidez en un smartphone de alta gama puede fallar en uno de gama baja.

La clave consiste en usar el modelo RAIL y centrarse en los usuarios. Descubre qué dispositivos son populares entre los usuarios y, luego, prueba tu página en ellos. Si la experiencia es mala en general, es posible que la página exceda las capacidades de memoria de esos dispositivos.

Supervisa el uso de la memoria en tiempo real con el Administrador de tareas de Chrome

Usa el Administrador de tareas de Chrome como punto de partida para tu investigación sobre el problema de memoria. El Administrador de tareas es un monitor en tiempo real que te indica cuánta memoria usa una página.

  1. Presiona Mayúsculas + Esc o ve al menú principal de Chrome y selecciona Más herramientas > Administrador de tareas para abrirlo.

    Abrir el Administrador de tareas

  2. Haz clic con el botón secundario en el encabezado de la tabla del Administrador de tareas y habilita Memoria de JavaScript.

    Se habilitó la memoria de JS en el encabezado del Administrador de tareas.

Estas dos columnas te brindan información diferente sobre la manera en la que tu página usa la memoria:

  • La columna Uso de memoria representa la memoria del SO. Los nodos del DOM se almacenan en la memoria del SO. Si este valor aumenta, significa que hay en curso un proceso de creación de nodos del DOM.
  • La columna JavaScript Memory representa el montón JS. Esta columna contiene dos valores. El valor que te interesa es el número en tiempo real (el número entre paréntesis). El número en tiempo real representa la cantidad de memoria que usan los objetos accesibles de tu página. Si este número aumenta, significa que se crean objetos nuevos o que aumenta el volumen de los objetos existentes.

    Administrador de tareas con el encabezado de memoria de JavaScript habilitado

Visualiza fugas de memoria con grabaciones de rendimiento

También puedes usar el panel Rendimiento como otro punto de partida para la investigación. El panel Rendimiento te ayuda a visualizar el uso de memoria de una página con el paso del tiempo.

  1. Abre el panel Rendimiento en DevTools.
  2. Habilita la casilla de verificación Memory.
  3. Realiza una grabación.

Para demostrar las grabaciones de memoria de rendimiento, ten en cuenta el siguiente código:

var x = [];

function grow() {
  for (var i = 0; i < 10000; i++) {
    document.body.appendChild(document.createElement('div'));
  }
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

Cada vez que se presiona el botón al que se hace referencia en el código, se agregan diez mil nodos div al cuerpo del documento y se inserta una cadena de un millón de caracteres x en el array x. Si se ejecuta este código, se realiza una grabación en Timeline como la que se muestra en la siguiente captura de pantalla:

Un ejemplo de crecimiento simple.

Primero, explicaremos la interfaz de usuario. El gráfico HEAP en el panel Overview (debajo de NET) representa el montón de JS. Debajo del panel Resumen, se encuentra el panel Contador. Aquí puedes ver el uso de la memoria desglosado por montón JS (como sucede con el gráfico HEAP en el panel Overview), documentos, nodos del DOM, objetos de escucha y memoria de la GPU. Si inhabilitas una casilla de verificación, esta se ocultará del gráfico.

Ahora, analizaremos el código en comparación con la captura de pantalla. Si observas el contador de nodos (el gráfico verde), puedes notar que coincide perfectamente con el código. El recuento de nodos aumenta en pasos discretos. Puedes suponer que cada aumento en el recuento de nodos es una llamada a grow(). El gráfico del montón JS (el gráfico azul) no es tan directo. Siguiendo las prácticas recomendadas, la primera disminución es en realidad una recolección forzada de elementos no usados (que se logra presionando el botón Collect garbage). A medida que la grabación avanza, puedes ver que el tamaño del montón de JS aumenta rápidamente. Esto es natural y esperable: el código JavaScript crea los nodos del DOM con cada clic en un botón y realiza mucho trabajo cuando crea la cadena de un millón de caracteres. El aspecto clave aquí es el hecho de que el montón JS termina más arriba que al comenzar (el “comienzo” en este caso es el punto después de la recolección forzada de elementos no usados). En el mundo real, si vieras este patrón de aumento del tamaño del montón JS o de los nodos, posiblemente indicaría una fuga de memoria.

Descubre fugas de memoria de árboles separados del DOM con capturas de pantalla del montón

Un nodo del DOM solo puede estar sujeto a la recolección de elementos no usados si no hay referencias a él en el árbol del DOM o el código JavaScript de la página. Se dice que un nodo está “separado” cuando se quita del árbol del DOM, pero JavaScript aún hace referencia a él. Los nodos separados del DOM son una causa común de fugas de memoria. En esta sección, aprenderás a usar los generadores de perfiles de montón de DevTools para identificar nodos separados.

A continuación, se ofrece un ejemplo simple de nodos separados del DOM.

var detachedTree;

function create() {
  var ul = document.createElement('ul');
  for (var i = 0; i < 10; i++) {
    var li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedTree = ul;
}

document.getElementById('create').addEventListener('click', create);

Cuando se hace clic en el botón al que se hace referencia en el código, se crea un nodo ul con diez elementos secundarios li. El código hace referencia a estos nodos, pero no existen en el árbol del DOM, por lo cual están separados.

Las capturas de pantalla del montón son una manera de identificar los nodos separados. Como su nombre lo indica, las capturas de pantalla del montón te muestran cómo se distribuye la memoria entre los objetos JS de tu página y los nodos DOM en el momento en que se toma la captura de pantalla.

Para crear una instantánea, abre DevTools y ve al panel Memory, selecciona el botón de selección Heap Snapshot y, luego, presiona el botón Take snapshot.

Se seleccionó el botón de selección Tomar instantánea del montón.

El procesamiento y la carga de la captura de pantalla pueden demorar un poco. Una vez finalizado esto, selecciónala en el panel izquierdo (llamado Heap snapshots).

Escribe Detached en el cuadro de entrada Filtro de clase para buscar árboles DOM separados.

Filtra los nodos separados.

Expande los triángulos para investigar un árbol separado.

Cómo investigar un árbol separado

Haz clic en un nodo para investigarlo más. En el panel Objects, puedes ver más información sobre el código que hace referencia a él. Por ejemplo, en la siguiente captura de pantalla, puedes ver que la variable detachedTree hace referencia al nodo. Para corregir esta fuga de memoria en particular, debes estudiar el código que usa detachedTree y asegurarte de que quite su referencia al nodo cuando ya no sea necesaria.

Cómo investigar un nodo desconectado

Identifica fugas de memoria del montón JS con líneas del tiempo de asignación

La línea de tiempo de asignación es otra herramienta que puede ayudarte a rastrear fugas de memoria en el montón JS.

Para demostrar la línea de tiempo de asignación, considera el siguiente código:

var x = [];

function grow() {
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

Cada vez que se presiona el botón al que se hace referencia en el código, se agrega una cadena de un millón de caracteres a la matriz x.

Para grabar un Cronograma de asignaciones, abre DevTools, ve al panel Memoria, selecciona el botón de selección Asignaciones en el cronograma, presiona el botón Grabar, realiza la acción que sospechas que está causando la fuga de memoria y, luego, presiona el botón Detener la grabación cuando termines.

A medida que realices la grabación, observa si aparecen barras azules en la línea de tiempo de asignación, como se muestra en la siguiente captura de pantalla.

Asignaciones nuevas en el cronograma de rendimiento

Las barras azules representan asignaciones de memoria nuevas. Esas asignaciones de memoria nuevas son candidatas para fugas de memoria. Puedes ampliar una barra para filtrar el panel Constructor y mostrar solo los objetos que se asignaron durante el período especificado.

Una línea de tiempo de asignación ampliada.

Expande el objeto y haz clic en su valor para ver más detalles en el panel Object. Por ejemplo, en la siguiente captura de pantalla, si observas los detalles del objeto que se asignó recientemente, podrás ver que se asignó a la variable x en el ámbito Window.

Son los detalles del objeto de un array de cadenas.

Investiga la asignación de memoria por función

Usa el tipo de perfil Muestra de asignación en el panel Memoria para ver la asignación de memoria por función de JavaScript.

Generador de perfiles de muestreo de asignaciones en el panel Memoria.

  1. Selecciona el botón de selección Muestreo de asignación. Si hay un trabajador en la página, puedes seleccionarlo como el objetivo de generación de perfiles en la ventana Select JavaScript VM instance.
  2. Presiona el botón Start.
  3. Realiza las acciones en la página que deseas investigar.
  4. Presiona el botón Stop cuando hayas terminado todas tus acciones.

DevTools te muestra un desglose de la asignación de memoria por función. La vista predeterminada es Heavy (Bottom Up), que muestra las funciones que asignaron la mayor cantidad de memoria en la parte superior.

Página de resultados del perfil de asignación.

Identifica los objetos retenidos por la referencia de JS

El perfil Elementos desconectados muestra los elementos desconectados que persisten porque el código JavaScript hace referencia a ellos.

Registra un perfil de elementos separados para ver los nodos y la cantidad de nodos HTML exactos.

Ejemplo de perfil de elementos desconectados.

Cómo detectar recolecciones frecuentes de elementos no utilizados

Si tu página parece pausarse con frecuencia, es posible que haya problemas con la recolección de elementos no utilizados.

Puedes usar el Administrador de tareas de Chrome o las grabaciones de memoria de Timeline para identificar recolecciones frecuentes de elementos no usados. En el Administrador de tareas, los valores de Memoria o Memoria de JavaScript que aumentan y disminuyen con frecuencia representan recolecciones frecuentes de elementos no usados. En las grabaciones de Timeline, los gráficos del montón JS o de recuento de nodos que aumentan y disminuyen con frecuencia indican que hay recolecciones frecuentes de elementos no usados.

Una vez identificado el problema, puedes usar la grabación de la línea de tiempo de asignación para descubrir los puntos en los que se asigna la memoria y las funciones que generan las asignaciones.