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

  1. 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.
  2. Evite WeakRef para lógica crítica — O momento da coleta é imprevisível; não confie em deref() retornar um valor.
  3. 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