Cómo solucionar problemas de memoria

Kayce Basques
Kayce Basques

Aprende a usar Chrome y las Herramientas para desarrolladores para encontrar problemas de memoria que afectan el rendimiento de la página, como fugas de memoria, aumento de memoria y recolecciones de elementos no utilizados frecuentes.

Resumen

  • Descubre cuánta memoria usa tu página con el Administrador de tareas de Chrome.
  • Visualiza el uso de memoria a lo largo del tiempo con las grabaciones de Timeline.
  • Identifica árboles del DOM desconectados (una causa común de fugas de memoria) con instantáneas del montón.
  • Descubre cuándo se asigna memoria nueva en tu montón de JS con las grabaciones de la línea de tiempo de asignación.
  • Identificar los elementos desconectados que retiene la referencia de JavaScript

Descripción general

En el espíritu del modelo de rendimiento RAIL, el enfoque de tus esfuerzos de rendimiento debe ser el usuario.

Los problemas de memoria son importantes porque, a menudo, los usuarios los perciben. 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 un error en la página hace que esta use cada vez más memoria con el tiempo.
  • El rendimiento de una página es constantemente malo. Esto posiblemente sea un síntoma de aumento de memoria. La sobrecarga de memoria se produce cuando una página usa más memoria de la necesaria para lograr una velocidad de la página óptima.
  • El rendimiento de una página se retrasa o parece pausarse con frecuencia. Es posible que esto sea un síntoma de recolecciones de elementos no usados frecuentes. La recolección de elementos no utilizados es cuando el navegador recupera memoria. El navegador decide cuándo sucede esto. Durante las recopilaciones, se detiene toda la ejecución de secuencias de comandos. Por lo tanto, si el navegador realiza muchas recolecciones de elementos no utilizados, la ejecución de la secuencia de comandos se pausará con frecuencia.

Sobrecarga de memoria: ¿Cuánto es "demasiado"?

Una fuga de memoria es fácil de definir. Si un sitio usa cada vez más memoria de forma progresiva, significa que hay una fuga. Sin embargo, la sobrecarga de memoria es un poco más difícil de identificar. ¿Qué se considera "usar demasiada memoria"?

No hay cifras exactas, ya que los diferentes dispositivos y navegadores tienen distintas capacidades. La misma página que se ejecuta sin problemas en un smartphone de alta gama podría fallar en uno de gama baja.

La clave aquí es usar el modelo RAIL y enfocarte en tus usuarios. Descubre qué dispositivos son populares entre tus usuarios y, luego, prueba tu página en esos dispositivos. Si la experiencia es mala de forma constante, es posible que la página supere las capacidades de memoria de esos dispositivos.

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

Usa el Administrador de tareas de Chrome como punto de partida para investigar el problema de memoria. El Administrador de tareas es un monitor en tiempo real que te indica cuánta memoria está usando una página.

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

    Abriendo el Administrador de tareas.

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

    Habilitación de la memoria de JS en el encabezado del Administrador de tareas

Estas dos columnas te indican diferentes aspectos sobre cómo tu página usa la memoria:

  • La columna Espacio en memoria representa la memoria del SO. Los nodos del DOM se almacenan en la memoria del SO. Si este valor aumenta, se están creando nodos del DOM.
  • La columna Memoria de JavaScript representa el montón de JS. Esta columna contiene dos valores. El valor que te interesa es el número en vivo (el número entre paréntesis). El número activo representa la cantidad de memoria que usan los objetos accesibles en tu página. Si este número aumenta, significa que se están creando objetos nuevos o que los existentes están creciendo.

    Administrador de tareas con el encabezado de memoria de JavaScript habilitado.

Visualiza las fugas de memoria con las grabaciones de rendimiento

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

  1. Abre el panel Rendimiento en las Herramientas para desarrolladores.
  2. Habilita la casilla de verificación Memoria.
  3. Cómo hacer una grabación.

Para demostrar las grabaciones de memoria de Rendimiento, considera 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 envía una cadena de un millón de caracteres x al array x. Cuando se ejecuta este código, se produce una grabación de Timeline como la que se muestra en la siguiente captura de pantalla:

Un ejemplo sencillo de crecimiento.

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

Ahora, un análisis del código en comparación con la captura de pantalla. Si observas el contador de nodos (el gráfico verde), puedes ver que coincide claramente con el código. La cantidad 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 de JS (el gráfico azul) no es tan sencillo. De acuerdo con las prácticas recomendadas, la primera caída es, en realidad, una recolección de basura forzada (que se logra presionando el botón collect garbage). A medida que avanza la grabación, puedes ver que el tamaño del montón de JS aumenta. Esto es natural y esperado: el código de JavaScript crea los nodos del DOM cada vez que se hace clic en el botón y realiza mucho trabajo cuando crea la cadena de un millón de caracteres. La clave aquí es el hecho de que el montón de JS termina más alto de lo que comenzó (el "comienzo" aquí es el punto después de la recolección de basura forzada). En el mundo real, si vieras este patrón de aumento del tamaño del montón de JS o del tamaño del nodo, podría significar una fuga de memoria.

Descubre fugas de memoria del árbol del DOM desconectado con instantáneas del montón

Un nodo del DOM solo se puede recolectar como elemento no utilizado cuando no hay referencias a él desde el árbol del DOM de la página ni desde el código JavaScript. Se dice que un nodo está "desconectado" cuando se quita del árbol del DOM, pero un código de JavaScript aún hace referencia a él. Los nodos del DOM separados son una causa común de las fugas de memoria. En esta sección, se explica cómo usar los generadores de perfiles del montón de las Herramientas para desarrolladores para identificar nodos desconectados.

Este es un ejemplo sencillo de nodos del DOM desconectados.

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);

Si haces clic en el botón al que se hace referencia en el código, se creará 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 que están desconectados.

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

Para crear una instantánea, abre las Herramientas para desarrolladores y ve al panel Memoria, selecciona el botón de selección Instantánea del montón y, luego, presiona el botón Tomar instantánea.

Se seleccionó el botón de selección Take heap snapshot.

La instantánea puede tardar un poco en procesarse y cargarse. Cuando termine, selecciónala en el panel izquierdo (llamado Instantáneas del montón).

Escribe Detached en el cuadro de entrada Class filter para buscar árboles del DOM desconectados.

Filtrado de nodos desconectados.

Expande los símbolos de intercalación para investigar un árbol separado.

Investiga un árbol separado.

Haz clic en un nodo para investigarlo más a fondo. 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, deberías estudiar el código que usa detachedTree y asegurarte de que quite su referencia al nodo cuando ya no sea necesario.

Investiga un nodo desconectado.

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

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

Para demostrar el funcionamiento de 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 al array x.

Para grabar un cronograma de asignación, abre las Herramientas para desarrolladores, 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 grabación cuando termines.

Mientras grabas, observa si aparecen barras azules en el cronograma de asignación, como en la siguiente captura de pantalla.

Nuevas asignaciones en el cronograma de rendimiento.

Esas barras azules representan nuevas asignaciones de memoria. Esas nuevas asignaciones de memoria son tus candidatas para las fugas de memoria. Puedes acercar 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 ves los detalles del objeto que se asignó recientemente, podrás ver que se asignó a la variable x en el alcance 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 Muestreo 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 asignación 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 destino de generación de perfiles en la ventana Select JavaScript VM instance.
  2. Presiona el botón Iniciar.
  3. Realiza las acciones en la página que quieras investigar.
  4. Presiona el botón Detener cuando hayas terminado todas las acciones.

Las Herramientas para desarrolladores te muestran 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 de JavaScript hace referencia a ellos.

Registra un perfil de Elementos desconectados para ver los nodos HTML exactos y el recuento de nodos.

Ejemplo de perfil de elementos desconectados.

Detecta recolecciones de elementos no utilizados frecuentes

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

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

Una vez que hayas identificado el problema, puedes usar una grabación de la línea de tiempo de asignación para averiguar dónde se asigna la memoria y qué funciones causan las asignaciones.