WeakMap e WeakRef: gerenciamento de memória
1. Fundamentos do Garbage Collection em JavaScript
O gerenciamento de memória em JavaScript depende do garbage collector (GC) do motor V8, que utiliza o algoritmo mark-and-sweep. Objetos mantidos por referências fortes nunca são coletados, mesmo quando não são mais necessários. Esse comportamento pode causar memory leaks em aplicações Node.js e React, especialmente quando referências a objetos desmontados ou callbacks antigos persistem.
Referências fortes impedem a coleta de objetos que não são mais utilizados. Por exemplo, um Map tradicional mantém referências fortes às suas chaves e valores, impedindo que sejam coletados mesmo quando o restante da aplicação não os utiliza mais.
2. WeakMap: Referências Fracas em Coleções
WeakMap é uma coleção que aceita apenas objetos como chaves e mantém referências fracas a essas chaves. A principal diferença para Map é que, se a única referência a um objeto for como chave de um WeakMap, esse objeto pode ser coletado pelo GC.
const weakMap = new WeakMap();
let objeto = { dados: 'importante' };
weakMap.set(objeto, 'metadado');
console.log(weakMap.get(objeto)); // 'metadado'
// Quando objeto se torna elegível para coleta
objeto = null;
// O par chave-valor é removido automaticamente do WeakMap
Regras importantes:
- Chaves devem ser obrigatoriamente objetos (não aceita primitivos)
- Não é iterável (não possui keys(), values(), entries() ou size)
- Ideal para metadados temporários e cache privado
3. Aplicações Práticas de WeakMap no Node.js
Cache de Operações Pesadas
const cache = new WeakMap();
function processarDados(objetoEntrada) {
if (cache.has(objetoEntrada)) {
return cache.get(objetoEntrada);
}
const resultado = realizarOperacaoPesada(objetoEntrada);
cache.set(objetoEntrada, resultado);
return resultado;
}
Gerenciamento de Listeners em Eventos
const listeners = new WeakMap();
class EventManager {
adicionarListener(objeto, callback) {
if (!listeners.has(objeto)) {
listeners.set(objeto, []);
}
listeners.get(objeto).push(callback);
}
notificar(objeto, evento) {
const callbacks = listeners.get(objeto);
if (callbacks) {
callbacks.forEach(cb => cb(evento));
}
}
}
Sessões Temporárias em Servidores
const sessoesAtivas = new WeakMap();
function criarSessao(usuario) {
const sessao = {
id: gerarId(),
criadaEm: Date.now(),
dados: {}
};
sessoesAtivas.set(usuario, sessao);
return sessao;
}
4. WeakMap em React: Estado Privado e Memoização
Evitando Memory Leaks em Hooks
import { useRef, useEffect } from 'react';
const cacheMetadados = new WeakMap();
function useMetadadosPrivados(componenteRef) {
useEffect(() => {
if (!cacheMetadados.has(componenteRef.current)) {
cacheMetadados.set(componenteRef.current, {
montado: true,
contador: 0
});
}
return () => {
// Quando o componente desmonta, a referência ao DOM
// é perdida e o WeakMap limpa automaticamente
};
}, []);
return cacheMetadados.get(componenteRef.current);
}
Cache de Computações em Componentes Funcionais
const resultadosCache = new WeakMap();
function useComputacaoPesada(objetoEntrada) {
const resultado = useMemo(() => {
if (resultadosCache.has(objetoEntrada)) {
return resultadosCache.get(objetoEntrada);
}
const novoResultado = realizarComputacao(objetoEntrada);
resultadosCache.set(objetoEntrada, novoResultado);
return novoResultado;
}, [objetoEntrada]);
return resultado;
}
5. WeakRef: Referências Fracas Diretas
WeakRef permite criar referências fracas diretamente a objetos, sem a necessidade de um Map. O método deref() retorna o objeto referenciado ou undefined se ele foi coletado.
let objeto = { dados: 'valioso' };
const refFraca = new WeakRef(objeto);
console.log(refFraca.deref()); // { dados: 'valioso' }
objeto = null;
// Em algum momento futuro, o GC pode coletar o objeto
// refFraca.deref() retornará undefined
FinalizationRegistry: Notificações de Coleta
const registry = new FinalizationRegistry((valor) => {
console.log(`Objeto com valor ${valor} foi coletado`);
});
let objeto = { id: 42 };
registry.register(objeto, 'meu-objeto');
objeto = null;
// Quando o GC coletar o objeto, o callback será chamado
Comparação:
| Tipo | Referência | GC impede coleta | Iterável |
|------|------------|------------------|----------|
| Forte | Sim | Sim | Sim |
| WeakMap | Fraca (chave) | Não | Não |
| WeakRef | Fraca | Não | Não |
6. Casos de Uso Avançados com WeakRef
Pool de Buffers Recicláveis em Node.js
const poolBuffers = new Map();
const finalizacao = new FinalizationRegistry((tamanho) => {
console.log(`Buffer de ${tamanho} bytes foi coletado`);
});
function obterBuffer(tamanho) {
if (poolBuffers.has(tamanho)) {
const ref = poolBuffers.get(tamanho);
const buffer = ref.deref();
if (buffer) return buffer;
}
const novoBuffer = Buffer.alloc(tamanho);
poolBuffers.set(tamanho, new WeakRef(novoBuffer));
finalizacao.register(novoBuffer, tamanho);
return novoBuffer;
}
Cache Evictável com Baixa Pressão de Memória
class CacheEvictavel {
constructor() {
this.cache = new Map();
this.registry = new FinalizationRegistry((chave) => {
this.cache.delete(chave);
});
}
set(chave, valor) {
this.cache.set(chave, new WeakRef(valor));
this.registry.register(valor, chave);
}
get(chave) {
const ref = this.cache.get(chave);
if (ref) return ref.deref();
return undefined;
}
}
Rastreamento sem Impedir Coleta
const rastreador = new WeakRef(new Set());
function rastrearObjeto(objeto) {
const set = rastreador.deref();
if (set) {
set.add(objeto);
}
}
7. Boas Práticas e Armadilhas
Quando NÃO usar WeakMap/WeakRef
- Não use WeakMap para dados que precisam persistir — Se você precisa que os dados sobrevivam ao ciclo de vida do objeto chave, use Map.
- Evite WeakRef para lógica crítica — O momento da coleta é imprevisível; não confie em
deref()retornar um valor. - Não abuse de FinalizationRegistry — Callbacks de finalização podem atrasar e não devem ser usados para lógica de negócios.
Debugging de Memory Leaks
// Chrome DevTools - Heap Snapshot
// 1. Abra DevTools > Memory
// 2. Selecione "Heap snapshot"
// 3. Filtre por "WeakMap" ou "WeakRef"
// Node.js heap snapshot
const v8 = require('v8');
const fs = require('fs');
function gerarSnapshot() {
const snapshot = v8.getHeapSnapshot();
const writeStream = fs.createWriteStream('heap.heapsnapshot');
snapshot.pipe(writeStream);
}
Limitações de Performance
WeakMap tem desempenho ligeiramente inferior ao Map para operações de get/set frequentes. Em React, evite usar WeakMap em renderizações muito frequentes — prefira useMemo e useCallback para memoização interna.
Cenários recomendados no ecossistema React:
- Cache de referências a elementos DOM desmontados
- Metadados de componentes que não devem impedir coleta
- Armazenamento de instâncias de classes externas associadas a componentes
Referências
- MDN Web Docs: WeakMap — Documentação oficial completa sobre WeakMap, incluindo sintaxe, métodos e exemplos práticos.
- MDN Web Docs: WeakRef — Guia detalhado sobre a API WeakRef e o método deref() para referências fracas diretas.
- V8 Blog: Gerenciamento de Memória no JavaScript — Artigo técnico do time V8 explicando o garbage collector e otimizações de memória.
- Node.js Documentation: Heap Snapshot — Documentação oficial sobre como gerar e analisar heap snapshots para debugging de memory leaks.
- React Documentation: Hooks e Performance — Guia oficial sobre useMemo e estratégias de memoização para evitar re-renderizações desnecessárias.
- JavaScript.info: WeakMap e WeakSet — Tutorial interativo com exemplos práticos de WeakMap e WeakSet no ecossistema JavaScript.
- Chrome DevTools: Memory Inspector — Guia oficial sobre como usar o Memory Inspector para identificar e resolver problemas de memória.