Corrigir problemas de memória

Kayce Basco
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, sobrecarga de memória e coletas de lixo frequentes.

Resumo

  • Saiba quanta memória sua página está usando no momento com o gerenciador de tarefas do Chrome.
  • Confira o uso da memória ao longo do tempo com os registros da linha do tempo.
  • Identifique árvores do DOM desconectadas (uma causa comum de vazamentos de memória) com snapshots de pilha.
  • Descubra quando nova memória está sendo alocada na heap JS com as gravações da Allocation Timeline.

Informações gerais

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

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

  • O desempenho de uma página piora progressivamente com o tempo. Isso é possivelmente 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 da página é consistentemente ruim. Isso é possivelmente um sintoma de sobrecarga de memória. A sobrecarga de memória ocorre quando uma página usa mais memória do que o necessário para ter a velocidade ideal.
  • O desempenho de uma página atrasa ou parece pausar com frequência. Isso é possivelmente um sintoma de coletas de lixo frequentes. A coleta de lixo ocorre quando o navegador recupera a memória. O navegador decide quando isso acontece. Durante as coletas, toda a execução do script é pausada. Portanto, se o navegador estiver coletando muito lixo, a execução do script será bastante pausada.

Excesso de memória: quanto é "excesso"?

Um vazamento de memória é fácil de definir. Se um site estiver usando cada vez mais memória progressivamente, isso significa que há um vazamento. Mas a ocupação excessiva da memória é um pouco mais difícil de identificar. O que se qualifica como "usando muita memória"?

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

A chave aqui é usar o modelo RAIL e focar nos seus usuários. Descubra quais dispositivos são populares entre os 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.

Monitore 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 investigar problemas de memória. O gerenciador de tarefas é um monitor em tempo real que informa quanta memória uma página está usando no momento.

  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.

    Como ativar a memória JS

Essas duas colunas informam detalhes sobre como a página está usando a memória:

  • A coluna Memory representa a memória nativa. Os nós DOM são armazenados na memória nativa. Se esse valor estiver aumentando, significa que os nós DOM estão sendo criados.
  • A coluna Memória JavaScript representa o heap JS. Esta coluna contém dois valores. O valor em que você tem interesse é o número ativo (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, isso significa que novos objetos estão sendo criados ou os atuais estão crescendo.

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

Também é possível usar o painel "Desempenho" como outro ponto de partida na sua investigação. O painel "Desempenho" ajuda a visualizar o uso da 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 os registros de memória de desempenho, considere o código abaixo:

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, 10 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 abaixo:

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 JS. Abaixo do painel Overview está o painel Counter. Aqui é possível ver o uso da memória detalhado por heap JS (igual ao gráfico HEAP no painel Overview), documentos, nós DOM, listeners e memória GPU. Quando uma caixa de seleção é desativada, ela não aparece no gráfico.

Agora, uma análise do código comparada à captura de tela. Se você observar 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. Você pode presumir que cada aumento na contagem de nós é uma chamada para grow(). O gráfico de heap 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, realizada pressionando o botão collect garbage. À medida que a gravação avança, o tamanho do heap do JS aumenta. Isso é natural e esperado: o código JavaScript está criando os nós DOM a cada clique de botão e trabalha muito quando cria a string de um milhão de caracteres. O principal aqui é o fato de que o heap JS termina mais alto do que começou (com o "início" sendo o ponto após a coleta de lixo forçada). No mundo real, se você perceber esse padrão de aumento do tamanho do heap do JS ou do nó, isso pode significar um vazamento de memória.

Descobrir vazamentos de memória da árvore do DOM desconectada com snapshots de heap

Um nó DOM só pode ser coletado como lixo quando não houver referências a ele na árvore DOM da página ou no código JavaScript. Um nó é considerado "desanexado" quando é removido da árvore do DOM, mas alguns JavaScript ainda fazem referência a ele. 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 heap do DevTools para identificar nós desconectados.

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

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. Por isso, são desconectados.

Os snapshots de heap são uma maneira de identificar nós desconectados. Como o nome indica, eles 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.

tirar um snapshot da alocação heap

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

Digite Detached na caixa de texto Filtro de classe para pesquisar árvores do DOM desconectadas.

filtragem de nós desconectados

Expanda os quilates para investigar uma árvore desconectada.

investigação de uma árvore desconectada

Os nós destacados em amarelo têm referências diretas a eles no código JavaScript. Os nós destacados em vermelho não têm referências diretas. Eles só estão vivos porque fazem parte da árvore do nó amarelo. Em geral, é recomendável se concentrar nos nós amarelos. Corrija seu código para que o nó amarelo não fique ativo por mais tempo do que o necessário. Além disso, você se livrará dos nós vermelhos que fazem parte da árvore do nó amarelo.

Clique em um nó amarelo para investigá-lo mais a fundo. No painel Objetos, há mais informações sobre o código que está se referindo a ele. Por exemplo, na captura de tela abaixo, é possível ver que a variável detachedTree está se referindo 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 ele não for mais necessário.

investigação de um nó amarelo

Identificar vazamentos de memória da 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 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 for enviado, uma string de um milhão de caracteres será adicionada à matriz x.

Para gravar uma Allocation Timeline, abra o DevTools, acesse o painel Profiles, selecione o botão de opção Record Allocation Timeline, pressione o botão Start, execute a ação que você suspeita que está causando o vazamento de memória e pressione o botão parar gravação (botão &quot;Parar gravação&quot;) quando terminar.

Enquanto você grava, observe se alguma barra azul aparece na linha do tempo de alocação, como na captura de tela abaixo.

novas alocações

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

cronograma de alocação com zoom aumentado

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

detalhes do objeto

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

Use o tipo Amostragem de alocação no painel Memória para visualizar a alocação de memória por função JavaScript.

Criador de perfil de alocação de gravação

  1. Selecione o botão de opção Amostragem de alocação. Se houver um worker na página, selecione-o como o destino da criação de perfil usando o menu suspenso ao lado do botão Start.
  2. Pressione o botão Start.
  3. Realize as ações na página que você quer investigar.
  4. Pressione o botão Stop quando tiver concluído todas as suas 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.

Perfil de alocação

Identificar coletas de lixo frequentes

Se a página parecer pausar com frequência, é possível que haja 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 crescentes e decrescentes de Memória ou Memória JavaScript representam coletas de lixo frequentes. Nas gravações da linha do tempo, gráficos de contagem de nós ou heap do JS com aumento e queda frequentes 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.