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 de memória, excesso de memória 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 do DOM desanexadas (uma causa comum de vazamentos de memória) com instantâneos de alocação heap.
  • Descubra quando uma nova memória está sendo alocada no heap de JS com as gravações da linha do tempo de alocação.
  • Identificar elementos removidos retidos pela referência do JavaScript.

Visão geral

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

Os problemas de memória são importantes porque geralmente são perceptíveis pelos usuários. Os usuários podem perceber problemas de memória das seguintes maneiras:

  • O desempenho 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 cada vez mais memória ao longo do tempo.
  • A performance de uma página é sempre ruim. Isso pode ser um sintoma de excesso de memória. O excesso de memória ocorre quando uma página usa mais memória do que o necessário para uma velocidade de página ideal.
  • A performance de uma página está atrasada ou parece pausar com frequência. Isso pode ser um sintoma de coletas de lixo frequentes. A coleta de lixo é quando o navegador recupera memória. O navegador decide quando isso acontece. Durante as coletas, todas as execuções de script são pausadas. Portanto, se o navegador estiver coletando muito lixo, a execução do script será pausada com frequência.

Inchaço de memória: quanto é "demais"?

Um vazamento de memória é fácil de definir. Se um site estiver usando cada vez mais memória, você tem um vazamento. Mas o aumento de memória é um pouco mais difícil de identificar. O que é considerado "usar muita memória"?

Não há números exatos aqui, porque dispositivos e navegadores diferentes têm recursos diferentes. A mesma página que funciona sem problemas em um smartphone sofisticado pode falhar em um smartphone mais simples.

O segredo é usar o modelo RAIL e focar nos usuários. Descubra quais dispositivos são mais usados pelos usuários e teste sua página neles. 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 investigar problemas 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.

    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 do DOM são armazenados na memória do SO. Se esse valor estiver aumentando, os nós do DOM estão sendo criados.
  • A coluna Memória JavaScript representa o heap de JS. Essa coluna contém dois valores. O valor que você quer é o número ativo (entre parênteses). O número ativo representa quanta memória 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 atuais 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

Também é possível usar o painel Desempenho como outro ponto de partida na investigação. O painel "Desempenho" 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. Faça uma gravação.

Para demonstrar as gravações de memória de Desempenho, 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);

Sempre 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 é enviada para a matriz x. A execução desse código produz uma gravação da linha do tempo como a captura de tela a seguir:

Um exemplo simples de crescimento.

Primeiro, uma explicação da interface do usuário. O gráfico HEAP no painel Visão geral (abaixo de NET) representa o heap de JS. Abaixo do painel Visão geral, está o painel Contador. Aqui, é possível ver o uso da memória detalhado por heap de JS (igual ao gráfico HEAP no painel Visão geral), documentos, nós do DOM, listeners e memória da GPU. Desativar uma caixa de seleção a oculta do gráfico.

Agora, uma análise do código em comparação com a captura de tela. Se você observar o contador de nós (o gráfico verde), vai notar que ele corresponde perfeitamente 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 heap de JS (o gráfico azul) não é tão simples. Para seguir as práticas recomendadas, a primeira queda é, na verdade, uma coleta de lixo forçada (conseguida ao pressionar o botão collect garbage). Conforme a gravação avança, é possível ver que o tamanho do heap de JS aumenta. Isso é natural e esperado: o código JavaScript está criando os nós do DOM a cada clique no botão e fazendo muito trabalho ao criar a string de um milhão de caracteres. O principal aqui é o fato de que o heap de JS termina mais alto do que começou (o "começo" aqui é o ponto após a coleta de lixo forçada). No mundo real, se você visse esse padrão de aumento do tamanho do heap de JS ou do nó, isso poderia significar um vazamento de memória.

Descobrir vazamentos de memória da árvore do DOM removida com instantâneos de alocação heap

Um nó do DOM só pode ser coletado como lixo quando não há referências a ele na árvore do DOM da página ou no código JavaScript. Um nó é considerado "separado" quando é removido da árvore do DOM, mas ainda é referenciado por algum JavaScript. Os nós DOM removidos são uma causa comum de vazamentos de memória. Nesta seção, você vai aprender a usar os criadores de perfil de alocação heap do DevTools para identificar nós desanexados.

Veja um exemplo simples de nós do DOM desanexados.

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 do DOM, então são desanexados.

Os instantâneos de alocação heap são uma forma de identificar nós desanexados. Como o nome indica, os instantâneos de alocação heap mostram como a memória é distribuída entre os objetos JS e os nós DOM da página no momento do instantâneo.

Para criar um instantâneo, abra o DevTools, acesse o painel Memória, selecione o botão de opção Instantâneo de alocação heap e pressione o botão Criar instantâneo.

Botão de opção &quot;Take heap snapshot&quot; selecionado.

O processamento e o carregamento do snapshot podem levar algum tempo. Quando terminar, selecione-o no painel à esquerda (chamado Instantâneos de alocação heap).

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

Filtragem de nós desanexados.

Abra os acentos circunflexos para investigar uma árvore separada.

Investigação de uma árvore separada.

Clique em um nó para investigar mais. No painel Objetos, é possível ver 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 está referenciando o nó. Para corrigir esse vazamento de memória específico, estude o código que usa detachedTree e garanta que ele remova a referência ao nó quando não for mais necessário.

Como investigar um nó separado.

Identificar vazamentos de memória de heap do JS com 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 de 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);

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

Para gravar uma Linha do tempo de alocação, abra o DevTools, acesse o painel Memória, selecione o botão de opção Alocações na linha do tempo, pressione o botão Gravar, execute 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 alguma barra azul aparece na Allocation Timeline, como na captura de tela abaixo.

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

Essas barras azuis representam novas alocações de memória. Essas novas alocações de memória são as candidatas a vazamentos de memória. É possível ampliar 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.

Expanda o objeto e clique no valor dele para ver mais detalhes no painel Object. Por exemplo, na captura de tela abaixo, ao visualizar os detalhes do objeto recém-alocado, é possível ver que ele foi alocado para a variável x no escopo Window.

Detalhes do objeto de uma matriz de strings.

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

Use o tipo de perfil Amostragem 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 Amostragem de alocação. Se houver um worker na página, você poderá selecioná-lo como o destino de criação de perfil na janela Selecionar a instância da VM JavaScript.
  2. Pressione o botão Iniciar.
  3. Realize as ações na página que você quer investigar.
  4. Pressione o botão Stop quando terminar todas as ações.

O DevTools mostra 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 os elementos removidos que persistem porque são referenciados pelo código JavaScript.

Registre um perfil de Elementos desanexados para conferir os nós HTML exatos e a contagem de nós.

Exemplo de perfil de elementos removidos.

Identificar 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, 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, gráficos de heap de JS ou de contagem de nós que sobem e descem com frequência 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.