Corrigir problemas de memória

Kayce Basques
Kayce Basques

Aprenda a usar o Chrome e o DevTools para encontrar problemas de memória que afetam o desempenho da página, incluindo vazamentos, inchaço e coletas de lixo frequentes.

Resumo

  • Descubra quanta memória sua página está usando com o Gerenciador de tarefas do Chrome.
  • Visualize o uso da memória ao longo do tempo com as gravações da linha do tempo.
  • Identifique árvores DOM separadas (uma causa comum de vazamentos de memória) com Heap Snapshots.
  • Descubra quando uma nova memória está sendo alocada na pilha do JS com as gravações da linha do tempo de alocação.
  • Identifique elementos separados retidos pela referência do JavaScript.

Visão geral

No espírito do modelo de performance RAIL, o foco dos seus esforços de performance deve ser os usuários.

Os problemas de memória são importantes porque muitas vezes são percebidos pelos usuários. Os usuários podem perceber problemas de memória das seguintes maneiras:

  • A performance de uma página piora progressivamente ao longo do tempo. Isso pode ser um sintoma de um vazamento de memória. Um vazamento de memória ocorre quando um bug na página faz com que ela use progressivamente mais e mais memória ao longo do tempo.
  • A performance de uma página é consistentemente ruim. Isso pode ser um sintoma de inchaço de memória. O inchaço de memória ocorre quando uma página usa mais memória do que o necessário para uma velocidade ideal.
  • O desempenho de uma página está atrasado ou parece ser pausado com frequência. Isso pode ser um sintoma de coleções de lixo frequentes. A coleta de lixo é quando o navegador recupera a memória. O navegador decide quando isso acontece. Durante as coleções, toda a execução do script é pausada. Portanto, se o navegador faz muita coleta de lixo, a execução do script será pausada com frequência.

Expansão de memória: o que é "demais"?

Um vazamento de memória é fácil de definir. Se um site estiver usando cada vez mais memória, significa que há um vazamento. Mas o inchaço de memória é um pouco mais difícil de identificar. O que é considerado "uso excessivo de memória"?

Não há números exatos aqui, porque diferentes dispositivos e navegadores têm recursos diferentes. A mesma página que funciona sem problemas em um smartphone de última geração pode apresentar falhas em um smartphone de baixo custo.

O importante aqui é usar o modelo RAIL e se concentrar nos usuários. Descubra quais dispositivos são populares entre seus usuários e teste a página nesses dispositivos. Se a experiência for consistentemente ruim, a página pode estar excedendo os recursos de memória desses dispositivos.

Monitorar o uso da memória em tempo real com o Gerenciador de tarefas do Chrome

Use o Gerenciador de tarefas do Chrome como ponto de partida para a investigação do problema de memória. O Gerenciador de tarefas é um monitor em tempo real que informa quanta memória uma página está usando.

  1. Pressione Shift + Esc ou acesse o menu principal do Chrome e selecione Mais ferramentas > Gerenciador de tarefas para abrir o Gerenciador de tarefas.

    Como abrir o Gerenciador de tarefas.

  2. Clique com o botão direito do mouse no cabeçalho da tabela do Gerenciador de tarefas e ative a Memória JavaScript.

    Ativação da memória JS no cabeçalho do Gerenciador de tarefas.

Essas duas colunas mostram coisas diferentes sobre como a página está usando a memória:

  • A coluna Consumo de memória representa a memória do SO. Os nós DOM são armazenados na memória do SO. Se esse valor estiver aumentando, os nós DOM estão sendo criados.
  • A coluna Memória JavaScript representa o heap do JS. Essa coluna contém dois valores. O valor de que você precisa é o número em tempo real (o número entre parênteses). O número em tempo real representa a quantidade de memória que os objetos acessíveis na página estão usando. Se esse número estiver aumentando, novos objetos estão sendo criados ou os objetos existentes estão crescendo.

    Gerenciador de tarefas com o cabeçalho de memória do JavaScript ativado.

Visualizar vazamentos de memória com gravações de desempenho

Você também pode usar o painel Performance como outro ponto de partida na investigação. O painel "Performance" ajuda a visualizar o uso de memória de uma página ao longo do tempo.

  1. Abra o painel Performance no DevTools.
  2. Marque a caixa de seleção Memória.
  3. Fazer uma gravação.

Para demonstrar as gravações de memória do Performance, considere o seguinte 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);

Toda vez que o botão referenciado no código é pressionado, dez mil nós div são anexados ao corpo do documento, e uma string de um milhão de caracteres x é inserida no array x. A execução desse código produz uma gravação da linha do tempo como a captura de tela abaixo:

Um exemplo simples de crescimento.

Primeiro, uma explicação da interface do usuário. O gráfico HEAP no painel Overview (abaixo de NET) representa o heap do JS. Abaixo do painel Visão geral, está o painel Contador. Aqui você pode ver o uso da memória dividido por heap do JS (o mesmo que o gráfico HEAP no painel Overview), documentos, nós DOM, listeners e memória da GPU. Desativar uma caixa de seleção oculta o item do gráfico.

Agora, uma análise do código em comparação com a captura de tela. Se você olhar para o contador de nós (o gráfico verde), vai notar que ele corresponde ao código. A contagem de nós aumenta em etapas discretas. É possível presumir que cada aumento na contagem de nós é uma chamada para grow(). O gráfico de pilha do JS (o gráfico azul) não é tão simples. De acordo com as práticas recomendadas, a primeira queda é uma coleta de lixo forçada (feita pressionando o botão coletar lixo). À medida que a gravação avança, é possível notar que o tamanho do heap do JS aumenta. Isso é natural e esperado: o código JavaScript está criando os nós DOM em cada clique no botão e fazendo muito trabalho ao criar a string de um milhão de caracteres. O ponto principal aqui é o fato de que a pilha do JS termina mais alta do que começou (o "início" aqui é o ponto após a coleta de lixo forçada). Na vida real, se você notar esse padrão de aumento do tamanho da pilha do JS ou do nó, isso pode significar um vazamento de memória.

Descubra vazamentos de memória da árvore do DOM desconectados com os snapshots do Heap

Um nó DOM só pode ser coletado como lixo quando não há referências a ele na árvore DOM da página ou no código JavaScript. Um nó é considerado "desconectado" quando é removido da árvore DOM, mas alguns JavaScripts ainda o referenciam. Nós DOM removidos são uma causa comum de vazamentos de memória. Esta seção ensina como usar os profilers de heap do DevTools para identificar nós separados.

Confira um exemplo simples de nós DOM separados.

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

Clicar no botão referenciado no código cria um nó ul com dez filhos li. Esses nós são referenciados pelo código, mas não existem na árvore DOM. Por isso, eles são separados.

Os snapshots de heap são uma maneira de identificar nós desconectados. Como o nome indica, os snapshots de heap mostram como a memória é distribuída entre os objetos JS e os nós DOM da página no momento do snapshot.

Para criar um snapshot, abra as Ferramentas do desenvolvedor e acesse o painel Memory, selecione o botão de opção Heap Snapshot e pressione o botão Take snapshot.

Botão de opção &quot;Tirar snapshot da pilha&quot; selecionado.

O processamento e o carregamento do snapshot podem levar algum tempo. Quando terminar, selecione-o no painel à esquerda (chamado Snapshots de heap).

Digite Detached na caixa de entrada Filtro de classe para pesquisar árvores DOM separadas.

Filtragem para nós desconectados.

Abra os carats para investigar uma árvore desconectada.

Como investigar uma árvore desconectada.

Clique em um nó para investigar mais. No painel Objects, você pode conferir mais informações sobre o código que faz referência a ele. Por exemplo, na captura de tela a seguir, é possível ver que a variável detachedTree faz referência ao nó. Para corrigir esse vazamento de memória específico, estude o código que usa detachedTree e verifique se ele remove a referência ao nó quando não for mais necessário.

Como investigar um nó desconectado.

Identificar vazamentos de memória de heap do JS com as Linhas do tempo de alocação

A Linha do tempo de alocação é outra ferramenta que pode ajudar a rastrear vazamentos de memória no heap do JS.

Para demonstrar a linha do tempo de alocação, considere o seguinte código:

var x = [];

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

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

Toda vez que o botão referenciado no código é enviado, uma string de um milhão de caracteres é adicionada à matriz x.

Para gravar uma linha do tempo de alocação, abra as Ferramentas do desenvolvedor, acesse o painel Memória, selecione o botão de opção Alocações na linha do tempo, pressione o botão Gravar, realize a ação que você suspeita que está causando o vazamento de memória e pressione o botão Parar gravação quando terminar.

Durante a gravação, observe se há barras azuis na linha do tempo de alocação, como na captura de tela a seguir.

Novas alocações na linha do tempo de performance.

Essas barras azuis representam novas alocações de memória. Essas novas alocações de memória são os candidatos para vazamentos de memória. É possível dar zoom em uma barra para filtrar o painel Constructor e mostrar apenas os objetos que foram alocados durante o período especificado.

Uma linha do tempo de alocação ampliada.

Abra o objeto e clique no valor dele para conferir mais detalhes no painel Object. Por exemplo, na captura de tela abaixo, ao conferir os detalhes do objeto que foi alocado recentemente, você pode ver que ele foi alocado para a variável x no escopo Window.

Detalhes do objeto de uma matriz de string.

Investigar a alocação de memória por função

Use o tipo de perfil Amostras de alocação no painel Memória para conferir a alocação de memória por função JavaScript.

Criador de perfil de amostragem de alocação no painel &quot;Memória&quot;.

  1. Selecione o botão de opção Alocação de amostragem. Se houver um worker na página, você poderá selecioná-lo como o destino do perfil na janela Selecionar instância da VM JavaScript.
  2. Pressione o botão Start.
  3. Realize as ações na página que você quer investigar.
  4. Pressione o botão Parar quando terminar todas as ações.

As Ferramentas do desenvolvedor mostram um detalhamento da alocação de memória por função. A visualização padrão é Heavy (Bottom Up), que mostra as funções que alocaram mais memória na parte de cima.

Página de resultados do perfil de alocação.

Identificar objetos retidos por referência JS

O perfil Elementos removidos mostra elementos removidos que persistem porque são referenciados pelo código JavaScript.

Grave um perfil de elementos separados para conferir os nós e a contagem de nós exatos do HTML.

Exemplo de perfil de elementos removidos.

Detectar coletas de lixo frequentes

Se a página parecer pausar com frequência, talvez você tenha problemas de coleta de lixo.

Você pode usar o Gerenciador de tarefas do Chrome ou as gravações de memória da Linha do tempo para identificar coletas de lixo frequentes. No Gerenciador de tarefas, os valores de Memória ou Memória JavaScript que aumentam e diminuem com frequência representam coletas de lixo frequentes. Nas gravações da linha do tempo, os gráficos de contagem de nós ou de pilha JS em ascensão e queda indicam coletas de lixo frequentes.

Depois de identificar o problema, use uma gravação da linha do tempo de alocação para descobrir onde a memória está sendo alocada e quais funções estão causando as alocações.