Back

Como Depurar Vazamentos de Memória em JavaScript

Como Depurar Vazamentos de Memória em JavaScript

Vazamentos de memória em JavaScript são assassinos silenciosos de desempenho. Sua aplicação começa rápida, mas após horas de uso, fica lenta. Os usuários reclamam de interfaces lentas, abas congeladas ou travamentos—especialmente em dispositivos móveis. O culpado? Memória que deveria ser liberada mas não é, acumulando-se até que sua aplicação sufoque.

Este guia mostra como identificar, diagnosticar e corrigir vazamentos de memória em JavaScript usando o profiler de memória do Chrome DevTools e técnicas comprovadas de depuração que funcionam em frameworks e ambientes modernos.

Principais Conclusões

  • Vazamentos de memória ocorrem quando a memória alocada não é liberada apesar de não ser mais necessária
  • O profiler de memória do Chrome DevTools oferece snapshots de heap e linhas do tempo de alocação para detecção de vazamentos
  • Padrões comuns de vazamento incluem nós DOM desanexados, event listeners acumulados e referências retidas por closures
  • Estratégias de prevenção incluem usar WeakMap para caches e implementar limpeza adequada nos ciclos de vida dos frameworks

Entendendo Vazamentos de Memória em JavaScript

Um vazamento de memória ocorre quando sua aplicação aloca memória mas falha em liberá-la depois que ela não é mais necessária. Em JavaScript, o garbage collector (coletor de lixo) automaticamente recupera memória não utilizada—mas apenas se não houver referências remanescentes a ela.

A distinção importa: alto uso de memória significa que sua aplicação usa muita memória mas permanece estável. Um vazamento de memória mostra consumo de memória crescendo continuamente que nunca se estabiliza, mesmo quando a carga de trabalho permanece constante.

Reconhecendo Sintomas de Vazamento de Memória

Fique atento a estes sinais de alerta em suas aplicações JavaScript:

  • O uso de memória aumenta constantemente ao longo do tempo sem diminuir
  • O desempenho degrada após uso prolongado
  • Abas do navegador ficam sem resposta ou travam
  • Usuários móveis relatam congelamentos da aplicação com mais frequência do que usuários desktop
  • O consumo de memória não diminui após fechar funcionalidades ou navegar para outra página

Detectando Vazamentos de Memória com Chrome DevTools

O profiler de memória do Chrome DevTools fornece o fluxo de trabalho mais confiável para depuração de snapshots de heap. Aqui está a abordagem sistemática:

Capturando e Comparando Snapshots de Heap

  1. Abra o Chrome DevTools (Ctrl+Shift+I ou Cmd+Option+I)
  2. Navegue até a aba Memory
  3. Selecione Heap snapshot e clique em Take snapshot
  4. Execute a ação suspeita de vazamento em sua aplicação
  5. Force a coleta de lixo (ícone de lixeira)
  6. Capture outro snapshot
  7. Selecione o segundo snapshot e mude para a visualização Comparison
  8. Procure por objetos com valores Delta positivos

Objetos que aumentam consistentemente entre snapshots indicam potenciais vazamentos. A coluna Retained Size mostra quanta memória seria liberada se aquele objeto fosse removido.

Usando Allocation Timeline para Análise em Tempo Real

A Allocation Timeline revela padrões de alocação de memória ao longo do tempo:

  1. Na aba Memory, selecione Allocation instrumentation on timeline
  2. Inicie a gravação e interaja com sua aplicação
  3. Barras azuis representam alocações; barras cinzas mostram memória liberada
  4. Barras azuis persistentes que nunca ficam cinzas indicam objetos retidos

Esta técnica se destaca na identificação de vazamentos durante interações específicas do usuário ou ciclos de vida de componentes em SPAs.

Padrões Comuns de Vazamento de Memória em JavaScript Moderno

Nós DOM Desanexados

Elementos DOM removidos do documento mas ainda referenciados em JavaScript criam nós DOM desanexados—um problema frequente em UIs orientadas a componentes:

// Vazamento: referência DOM persiste após remoção
let element = document.querySelector('.modal');
element.remove(); // Removido do DOM
// variável element ainda mantém referência

// Correção: Limpe a referência
element = null;

Busque por “Detached” nos filtros de snapshot de heap para encontrar estes nós órfãos.

Acumulação de Event Listeners

Event listeners que não são removidos quando componentes são desmontados acumulam ao longo do tempo:

// Exemplo React - vazamento de memória
useEffect(() => {
  const handler = () => console.log('resize');
  window.addEventListener('resize', handler);
  // Faltando limpeza!
}, []);

// Correção: Retorne função de limpeza
useEffect(() => {
  const handler = () => console.log('resize');
  window.addEventListener('resize', handler);
  return () => window.removeEventListener('resize', handler);
}, []);

Referências Retidas por Closures

Closures mantêm variáveis do escopo pai vivas, potencialmente retendo objetos grandes desnecessariamente:

function createProcessor() {
  const hugeData = new Array(1000000).fill('data');
  
  return function process() {
    // Esta closure mantém hugeData na memória
    return hugeData.length;
  };
}

const processor = createProcessor();
// hugeData permanece na memória enquanto processor existir

Técnicas Avançadas de Depuração

Analisando Caminhos de Retenção (Retainer Paths)

O caminho de retenção mostra por que um objeto permanece na memória. Em snapshots de heap:

  1. Clique em um objeto suspeito de vazamento
  2. Examine o painel Retainers abaixo
  3. Siga a cadeia desde as raízes GC para entender o que está mantendo a referência

A distância da raiz GC indica quantas referências devem ser quebradas para liberar o objeto.

Profiling de Memória em Node.js

Para aplicações Node.js, use o protocolo inspector do V8:

# Habilite snapshots de heap no Node.js
node --inspect app.js

Conecte o Chrome DevTools a chrome://inspect para as mesmas capacidades de profiling de memória em código server-side.

Estratégias de Prevenção para Aplicações em Produção

WeakMap para Gerenciamento de Cache

Substitua caches de objetos por WeakMap para permitir coleta de lixo:

// Map regular previne GC
const cache = new Map();
cache.set(element, data); // element não pode ser coletado

// WeakMap permite GC quando element não é referenciado em outro lugar
const cache = new WeakMap();
cache.set(element, data); // element pode ser coletado

Testes Automatizados de Memória

Integre detecção de vazamento de memória em seu pipeline de CI usando Puppeteer:

const puppeteer = require('puppeteer');

async function detectLeak() {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  
  // Capture snapshot inicial
  const metrics1 = await page.metrics();
  
  // Execute ações
  await page.click('#button');
  
  // Force GC e meça novamente
  await page.evaluate(() => window.gc());
  const metrics2 = await page.metrics();
  
  // Verifique crescimento de memória
  const memoryGrowth = metrics2.JSHeapUsedSize / metrics1.JSHeapUsedSize;
  if (memoryGrowth > 1.1) {
    throw new Error('Potencial vazamento de memória detectado');
  }
  
  await browser.close();
}

Padrões de Limpeza Específicos por Framework

Cada framework tem seus padrões de gerenciamento de memória:

  • React: Limpe nos retornos do useEffect, evite closures obsoletas em event handlers
  • Vue: Destrua adequadamente watchers e event listeners no beforeUnmount
  • Angular: Cancele a inscrição de observables RxJS usando takeUntil ou async pipe

Conclusão

Depurar vazamentos de memória em JavaScript requer análise sistemática usando o profiler de memória do Chrome DevTools, compreensão de padrões comuns de vazamento e implementação de medidas preventivas. Comece com comparações de snapshots de heap para identificar objetos crescentes, trace seus caminhos de retenção para encontrar causas raiz e aplique padrões de limpeza apropriados ao framework. O profiling regular de memória durante o desenvolvimento detecta vazamentos antes que eles cheguem à produção, onde são mais difíceis de diagnosticar e mais custosos de corrigir.

Perguntas Frequentes

Clique no ícone de lixeira na aba Memory antes de capturar snapshots. Você também pode acioná-la programaticamente no console com window.gc() se o Chrome for iniciado com a flag --expose-gc.

Shallow size é a memória usada pelo próprio objeto. Retained size inclui o objeto mais todos os objetos que ele referencia que seriam liberados se este objeto fosse removido.

Sim, aplicações Node.js podem vazar memória através de variáveis globais, conexões não fechadas, arrays crescentes ou listeners de event emitters. Use as mesmas técnicas do Chrome DevTools via node --inspect.

Faça profiling após implementar funcionalidades importantes, antes de releases e sempre que usuários relatarem degradação de desempenho. Configure testes automatizados de memória no CI para detectar vazamentos cedo.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay