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, 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 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 instantâneos de pilha.
  • Descubra quando nova memória é alocada à pilha do JS com as gravações da Allocation Timeline.

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 cada vez mais memória ao longo do 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 é um sintoma de coletas de lixo frequentes. 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 há números fixos aqui, porque dispositivos e navegadores diferentes têm recursos diferentes. A mesma página que é executada suavemente em um smartphone sofisticado 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 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 do JS

Essas duas colunas contêm informações diferentes sobre a forma como a página usa a memória:

  • A coluna Memória representa a memória nativa. Os nós de DOM são armazenados na memória nativa. Se esse valor estiver aumentando, os nós do DOM estão sendo criados.
  • A coluna Memória JavaScript representa a heap 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.

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 os registros de memória de performance, 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);

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 produz uma gravação da linha do tempo como a seguinte captura de tela:

exemplo de crescimento simples

Primeiro, uma explicação sobre a interface do usuário. O gráfico HEAP no painel Visão geral (abaixo de NET) representa o heap JS. Abaixo do painel Visão geral fica o painel Contador. Aqui, o uso da memória é exibido 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. É possível presumir que cada aumento na contagem de nós seja 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 do 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.

Veja a seguir 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);

Um clique 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 e, por isso, estão desconectados.

Os snapshots de heap são uma maneira 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.

criar um snapshot de pilha

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 Class filter para buscar árvores DOM desconectadas.

filtrar por nós desconectados

Expanda os quilates para investigar uma árvore desconectada.

investigando árvore desconectada

Os nós destacados em amarelo fazem referência direta a eles no código JavaScript. Os nós destacados em vermelho não têm referências diretas. Eles só estão ativos porque fazem parte da árvore do nó amarelo. Em geral, você deve se concentrar nos nós amarelos. Altere o código para que o nó amarelo não fique ativo por mais tempo do que precisa e se livre dos nós vermelhos que fazem parte da árvore do nó amarelo.

Clique em um nó amarelo para examiná-lo em detalhes. 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 abaixo, é 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.

investigando um nó amarelo

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 linha do tempo de alocação, abra o DevTools, acesse o painel Profiles, selecione o botão de opção Record Allocation Timeline, pressione o botão Start, realize a ação que você suspeita estar causando o vazamento de memória e pressione o botão stop recording (botão &quot;Parar gravação&quot;) quando terminar.

Durante a gravação, 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 ter vazamentos de memória. É possível aplicar zoom em uma barra para filtrar o painel Construtor e mostrar apenas os objetos que foram alocados durante o período especificado.

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

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

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

Criador de perfil de alocação de registro

  1. Selecione o botão de opção Amostrar a alocação. Se houver um worker na página, ele poderá ser selecionado 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 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 é Strong (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 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.