Corrigir problemas de memória

Kayce Basques
Kayce Basques

Saiba como usar o Chrome e o DevTools para encontrar problemas de memória que afetam o desempenho da página, incluindo vazamentos de memória, ocupação excessiva de memória e coletas de lixo frequentes.

Resumo

  • Saiba quanta memória sua página está usando com o Gerenciador de tarefas do Chrome.
  • Confira o uso de memória ao longo do tempo com as gravações da linha do tempo.
  • Identifique árvores do DOM desconectadas (uma causa comum de vazamentos de memória) com os resumos de pilha.
  • Descubra quando nova memória é alocada à pilha do JS com as gravações da Allocation Timeline.
  • Identifique elementos separados retidos pela referência do JavaScript.

Visão geral

De acordo com o modelo de desempenho RAIL, os esforços de desempenho devem se concentrar nos usuários.

Os problemas de memória são importantes porque são frequentemente percebidos 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 é possivelmente um sintoma de 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 com o tempo.
  • A performance de uma página é consistentemente ruim. Isso é possivelmente um sintoma de ocupação excessiva da memória. A ocupação excessiva da memória ocorre quando uma página usa mais memória do que o necessário para obter a maior velocidade.
  • O desempenho de uma página atrasa ou parece pausar com frequência. Isso é possivelmente um sintoma de coleta de lixo frequente. A coleta de lixo ocorre quando o navegador recupera memória. O navegador decide quando isso acontece. Durante as coletas, toda execução de script é pausada. Portanto, se o navegador estiver coletando muito lixo, a execução de scripts será pausada muitas vezes.

Ocupação excessiva de memória: quanto é "demais"?

É fácil definir um vazamento de memória. Se um site estiver usando progressivamente mais e mais memória, há um vazamento. Mas a ocupação excessiva da memória é um pouco mais difícil de detectar. O que é considerado "uso excessivo de memória"?

Não existem números padrão porque dispositivos e navegadores diferentes têm recursos distintos. A mesma página que funciona bem em um smartphone de ponta pode falhar em um smartphone mais simples.

A saída é usar o modelo RAIL e manter o foco nos usuários. Descubra quais dispositivos são populares com seus usuários e teste sua página neles. Se a experiência for consistentemente ruim, a página poderá estar excedendo os recursos de memória desses dispositivos.

Monitorar o uso de 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 contêm informações diferentes sobre a forma como a página usa 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 a pilha JS. Esta coluna contém dois valores. O valor que você deve olhar é o número ativo (o número entre parênteses). O número ativo representa quanta memória os objetos acessíveis na sua 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 para a 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 este 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);

Todas as vezes 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 na matriz x. A execução desse código gera uma gravação de Timeline semelhante à 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 a pilha JS. Abaixo do painel Visão geral está o painel Contador. Aqui, o uso da memória é mostrado dividido por pilha do JS (como no gráfico HEAP no painel Overview), documentos, nós do DOM, listeners e memória da GPU. Desative uma caixa de seleção para ocultá-la do gráfico.

Agora, uma análise do código comparada com a captura de tela. Se você observar o contador de nós (o gráfico verde), verá que ele corresponde exatamente ao código. A contagem de nós aumenta em etapas discretas. Você pode presumir que cada aumento na contagem de nós é uma chamada para grow(). O gráfico da pilha JS (o gráfico azul) não é tão simples. De acordo com as práticas recomendadas, o primeiro fundo é, na verdade, uma coleta de lixo forçada (resultante do pressionar do botão collect garbage). À medida que a gravação avança, você pode notar que o tamanho da pilha JS aumenta. Isso é natural e esperado: o código JavaScript está criando os nós do DOM a cada clique no botão e trabalhando muito quando cria a string de um milhão de caracteres. O principal fator aqui é o fato de que a pilha JS encerra mais alta do que começou (o "início" aqui é o ponto após a coleta de lixo forçada). No mundo real, se você perceber esse padrão de tamanho crescente de pilha de JS ou nó, isso poderá significar um vazamento de memória.

Descobrir vazamentos de memória da árvore do DOM desconectada com instantâneos de pilha

Um nó do DOM somente pode ser coletado como lixo quando não for referenciado pela árvore do DOM ou pelo código JavaScript da página. Um nó é considerado "desconectado" quando é removido da árvore do DOM, mas ainda tem referências no JavaScript. Nós do DOM desconectados são uma causa comum de vazamentos de memória. Esta seção ensina como usar os criadores de perfil de pilha do DevTools para identificar nós desconectados.

Confira um exemplo simples de nós de 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);

Um clique no botão referenciado no código cria um nó ul com dez filhos li. Estes nós são referenciados pelo código, mas não existem na árvore do DOM. Portanto, estão desconectados.

Os snapshots de pilha são uma forma de identificar nós desconectados. Como o nome indica, os snapshots de pilha 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 o DevTools 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 instantâneo podem demorar algum tempo. Depois que terminar, selecione-o no painel à esquerda (chamado de Snapshots de pilha).

Digite Detached na caixa de entrada Class filter para pesquisar árvores DOM desconectadas.

Filtragem para nós desconectados.

Expanda os quilates 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 está fazendo referência a ele. Por exemplo, na captura de tela a seguir, você pode 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ó desconectado.

Identificar vazamentos de memória na pilha JS com Allocation Timelines

A Allocation Timeline é outra ferramenta que pode ajudar a rastrear vazamentos de memória na pilha JS.

Para demonstrar o Allocation Timeline, considere este código:

var x = [];

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

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

Todas as vezes que o botão referenciado no código é acionado, uma string de um milhão de caracteres é adicionada à matriz x.

Para gravar uma Timeline 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, 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 alguma barra azul aparece 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 seus candidatos a ter vazamentos de memória. Você pode aumentar o zoom em uma barra e filtrar o painel Constructor para mostrar apenas os objetos que foram alocados durante o período especificado.

Uma linha do tempo de alocação com zoom.

Expanda o objeto e clique no valor para conferir mais detalhes no painel Object. Por exemplo, na captura de tela abaixo, ao conferir os detalhes do objeto recém-alocado, você vai notar 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 do JavaScript.

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

  1. Selecione o botão de opção Amostrar a alocação. 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.

O DevTools mostra a distribuição da alocação de memória por função. A visualização padrão é Heavy (Bottom Up), que mostra na parte de cima as funções que alocaram mais memória.

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.

Identificar coletas de lixo frequentes

Se uma página aparentar estar pausando com frequência, poderão estar ocorrendo problemas de coleta de lixo.

Você pode usar o Gerenciador de tarefas do Chrome ou os registros de memória da Timeline para identificar coletas de lixo frequentes. No Gerenciador de tarefas, os valores de Memória ou Memória JavaScript em aumento e diminuição frequentes representam coletas de lixo frequentes. Nas gravações da Timeline, gráficos de contagem de nós ou pilha JS frequentemente ascendentes e descendentes indicam coletas de lixo frequentes.

Depois de identificar o problema, você pode usar uma gravação da Allocation Timeline para descobrir onde a memória está sendo alocada e quais funções estão causando as alocações.