# Memory leak Node.js: como debugar em 6 passos

> Memory leak em Node.js exige diagnóstico sistemático para identificação de referências retidas. O processo de debugging envolve seis etapas: monitorar uso de memória, gerar heap snapshots, comparar snapshots, analisar retentores, corrigir referências e validar a correção. Sinais de alerta incluem crescimento contínuo de memória e degradação de performance. Heap snapshots permitem visualizar objetos retidos e identificar padrões de vazamento. A correção de referências elimina a causa raiz do vazamento.

*PosUp · Apps e Software · 31 de julho de 2026 · Gustavo Rennó*

Descubra como debugar memory leaks em Node.js com um passo a passo objetivo: de sinais de alerta a heap snapshots e correção de referências.

Memory leak em Node.js não aparece em testes rápidos. Ele se manifesta em produção, após horas ou dias de uso, quando o processo consome cada vez mais RAM até derrubar o serviço. Este guia mostra um caminho objetivo para identificar a causa, sem depender de ferramentas caras ou de tentativa e erro. Ao final, você terá um método reproduzível para diagnosticar e corrigir vazamentos.

## Pré-requisitos

Antes de começar, verifique se você tem:

- Node.js instalado (versão 12 ou superior para acesso ao módulo v8 e process.memoryUsage()).
- Acesso ao código em execução, de preferência em ambiente de staging que reproduza o cenário de produção.
- Uma carga de teste ou script que simule o uso real da aplicação.

Sem esses itens, o diagnóstico fica limitado a suposições. O objetivo é transformar o vazamento em um dado observável.

## Passo 1: Confirme que há um vazamento

O primeiro erro comum é assumir que qualquer crescimento de memória é um leak. Em Node.js, o V8 aumenta o heap conforme a demanda, e isso não é necessariamente um problema. Para confirmar, monitore o uso de memória ao longo do tempo sob carga constante.

Use process.memoryUsage() em intervalos regulares e registre o valor de heapUsed e rss. Se o heapUsed crescer de forma contínua, mesmo após o GC rodar, há um indício forte de vazamento. Se o crescimento estabilizar, pode ser apenas o heap se ajustando.

Dica: rode o processo por pelo menos 24 horas ou até que a memória dobre em relação ao valor inicial. Um crescimento de 10% em uma hora pode ser normal; dobrar em um dia não é.

## Passo 2: Capture heap snapshots

O heap snapshot é a fotografia do estado da memória em um dado momento. O Node.js expõe isso via v8.getHeapSnapshot(). O passo é capturar dois snapshots: um no início do processo, quando a memória está baixa, e outro depois de horas de uso ou quando o vazamento for evidente.

const v8 = require('v8'); const fs = require('fs');

const snapshot = v8.getHeapSnapshot(); snapshot.pipe(fs.createWriteStream('snapshot.heapsnapshot'));

Compare os dois arquivos. A diferença mostra quais objetos foram alocados e não liberados. Se um tipo de objeto específico domina a diferença, ele é o suspeito principal.

Erro comum: capturar apenas um snapshot. Um único snapshot não mostra crescimento, apenas o estado atual. Sem o comparativo, você não sabe o que é retido indevidamente.

## Passo 3: Use o Chrome DevTools para analisar

Abra o Chrome e acesse chrome://inspect. Conecte ao processo Node.js em execução e vá para a aba "Memory". Carregue os snapshots capturados e use a comparação para filtrar por "Objects allocated between snapshots".

O DevTools mostra a árvore de retenção: quem segura a referência para o objeto. É aqui que você descobre o ponto exato do vazamento. Por exemplo, um Set que nunca é limpo, ou um EventEmitter com listeners acumulados.

Dica: preste atenção em closures e variáveis globais. Elas são as causas mais comuns de referências retidas em JavaScript.

## Passo 4: Procure por listeners e timers

Listeners de eventos são a causa número um de memory leak em Node.js. Cada on() adiciona uma função à memória. Se você registra um listener dentro de uma função que é chamada repetidamente, sem remover, ele se acumula.

Use process.getActiveResourcesInfo() (Node 17+) para listar timers, sockets e handles ativos. Se o número de timers cresce sem parar, você tem um problema claro. A correção é sempre remover o listener com off() ou usar once() quando a intenção é ouvir uma única vez.

Exemplo: um servidor HTTP que adiciona um listener de data em cada request, sem remover, vaza memória a cada requisição.

## Passo 5: Examine caches e variáveis globais

Caches são traiçoeiros. Um Map global que armazena resultados de consultas pode crescer indefinidamente se não houver limite. O mesmo vale para WeakMap quando usado incorretamente, embora ele seja mais seguro por natureza.

Revise o código em busca de variáveis declaradas sem let ou const no escopo global, ou objetos anexados ao global. Eles nunca são coletados enquanto o processo viver.

Dica: se o cache é necessário, defina um tamanho máximo ou um TTL. Um Map com limite de 1000 entradas evita o crescimento infinito sem sacrificar a performance.

## Passo 6: Corrija e valide

Aplique a correção identificada, seja remover listeners, limpar caches ou ajustar escopo de variáveis. Depois, repita o monitoramento do Passo 1. O heapUsed deve se estabilizar ou crescer de forma previsível, sem dobrar.

Erro comum: corrigir um vazamento e não testar sob a mesma carga. O vazamento pode ser parcial, e a correção apenas reduz a velocidade, não elimina. Teste por pelo menos 48 horas em staging antes de promover para produção.

## Checklist rápido

- [ ] Monitorei heapUsed por 24 horas e confirmei crescimento contínuo.
- [ ] Capturei dois heap snapshots em momentos distintos.
- [ ] Analisei a diferença no Chrome DevTools e identifiquei o objeto retido.
- [ ] Verifiquei listeners, timers e caches globais.
- [ ] Corrigi a causa e validei sob carga por 48 horas.

## FAQ

### Como detectar memory leak em Node.js sem ferramentas externas?

Use process.memoryUsage() em intervalos regulares e registre heapUsed. Se o valor crescer continuamente sob carga constante, há indício de vazamento. Para confirmar, capture heap snapshots com v8.getHeapSnapshot() e compare os arquivos. O módulo nativo v8 e o process são suficientes para um diagnóstico inicial.

### O que causa memory leak em Node.js?

As causas mais comuns são listeners de eventos não removidos, timers que nunca são limpos, caches sem limite e variáveis globais acidentais. Referências retidas em closures também aparecem com frequência. Em todos os casos, o padrão é o mesmo: um objeto que deveria ser coletado pelo GC permanece acessível por uma referência ativa.

### Memory leak em Node.js é um problema de código ou de infraestrutura?

Na maioria dos casos, é um problema de código. A infraestrutura pode acelerar o vazamento, mas a causa raiz está em referências não liberadas no JavaScript. Exceções incluem módulos nativos mal escritos ou drivers de banco com bugs, mas esses são raros. Comece a investigação pelo código da aplicação.

### Qual a diferença entre heapUsed e rss no Node.js?

heapUsed é a memória alocada pelo V8 para objetos JavaScript. rss (resident set size) é a memória total do processo, incluindo heap, stack e código nativo. Um vazamento de JavaScript aparece primeiro em heapUsed. Se rss cresce mas heapUsed fica estável, o problema pode estar em buffers nativos ou módulos C++.

### Quando devo me preocupar com o crescimento de memória?

Quando o crescimento é contínuo e não estabiliza, mesmo após o garbage collector rodar. Um pico seguido de queda é normal. Um aumento progressivo que dobra a memória em menos de 24 horas sob carga constante é um sinal de alerta. Monitore também o tempo de resposta, pois vazamentos geralmente degradam a performance antes de derrubar o processo.

---

Fonte (canonical): https://posup.com.br/apps-e-software/memory-leak-nodejs-como-debugar-em-6-passos/
