DevGuideTK
← Trilha Backend

Gerenciamento de memória

Pilha, heap e coletor de lixo, e de onde vêm os vazamentos de memória em linguagens gerenciadas.

As decisões de memória são quatro: como configurar o runtime para o container, como evitar vazamentos no desenho do código, como acompanhar a memória em produção e como investigar quando ela cresce.

Configurar cada runtime para o limite do container

LinguagemO que o runtime faz por padrão num container de 512 MBO que configurar
NodeLimita o heap a cerca de metade da memória do container (259 MB no nosso teste)--max-old-space-size para ajustar, deixando espaço para memória fora do heap
PythonNão tem limite próprio; usa o que precisar até o sistema operacional matar o processoMonitorar e limitar o tamanho de caches e filas na aplicação
GoNão conhece o limite; coleta quando o heap dobra em relação aos dados em usoGOMEMLIMIT em cerca de 80 a 90% do limite do container
JavaUsa 25% da memória do container para o heap (123 MB no teste)-XX:MaxRAMPercentage entre 60 e 75
C#Usa 75% da memória do containerGeralmente nada; DOTNET_GCHeapHardLimitPercent para ajustar

Nenhuma configuração deve dar ao heap toda a memória do container. Todo processo usa memória fora do heap: o próprio runtime, as threads e as pilhas delas, buffers de rede, bibliotecas nativas, o código compilado. Deixe de 20 a 40% de folga.

Os dois lados do erro

ConfiguraçãoSintoma
Heap pequeno demais para o containerErros de falta de memória do runtime com o container quase vazio, ou o coletor rodando o tempo todo
Heap grande demais, ou sem limiteO sistema operacional mata o processo sem mensagem (OOMKilled)

Evitar vazamentos no desenho

PráticaComo aplicar
Todo cache tem limiteTamanho máximo, expiração, ou os dois; descarte do item menos usado recentemente (LRU)
Nada global que só cresceColeções globais com limite, ou trocadas por métricas e logs
Ouvintes com donoTodo registro de ouvinte tem um cancelamento correspondente, de preferência no mesmo trecho de código
Tarefas com fimGoroutines e tarefas assíncronas recebem um contexto ou token de cancelamento
Temporizadores canceladosTodo setInterval ou temporizador tem um clear correspondente
Dados grandes em fluxoArquivos e resultados grandes são processados em partes, sem carregar tudo na memória

O último ponto não é vazamento, mas derruba serviços do mesmo jeito: uma rota que carrega um arquivo de 1 GB ou uma consulta de um milhão de linhas inteiras na memória. Fluxos (streams), paginação e cursores do banco resolvem.

Cache em memória ou cache externo

Cache em memória com limiteRedis ou outro cache externo
VelocidadeA mais altaUma ida e volta pela rede
Memória do serviçoOcupa memória do processo, entra na conta do containerNão ocupa
Várias cópias do serviçoCada uma tem o seu, possivelmente diferenteCompartilhado
Risco de vazamentoSe o limite for esquecidoControlado pela política de descarte do Redis

O tópico de cache aprofunda a escolha. Para a memória, a regra é: se for em memória, com limite.

Acompanhar a memória em produção

MétricaOnde verSinal de alerta
Memória do processo (RSS)Métricas do container, docker stats, KubernetesPerto do limite do container
Heap usado depois de cada coletaMétricas do runtime (V8, JVM, .NET, Go)Subindo continuamente com carga estável
Frequência e duração das coletasMétricas do runtimeColetas cada vez mais frequentes: o heap está ficando pequeno
Reinícios por falta de memóriaKubernetes (OOMKilled), logs do orquestradorQualquer ocorrência

O sinal mais confiável de vazamento é o heap depois da coleta crescendo ao longo de horas ou dias, sem voltar ao patamar anterior. A memória total do processo oscila naturalmente; o que sobra depois de cada coleta, não.

Investigar um vazamento

O roteiro é parecido em todas as linguagens:

  1. Confirmar o crescimento: o heap depois das coletas sobe com carga estável?
  2. Capturar o heap duas vezes, com um intervalo de tempo e de tráfego entre as capturas.
  3. Comparar as capturas: quais tipos de objeto cresceram em quantidade e tamanho?
  4. Seguir as referências: o que mantém esses objetos alcançáveis? Quase sempre é uma coleção global, um cache ou um ouvinte.
LinguagemFerramenta para capturar e analisar o heap
Node--heapsnapshot-signal=SIGUSR2 ou v8.writeHeapSnapshot(), analisado no Chrome DevTools
Pythontracemalloc (da biblioteca padrão), memray
Gopprof com o perfil heap
Javajcmd <pid> GC.heap_dump, analisado no Eclipse MAT ou no VisualVM
C#dotnet-gcdump ou dotnet-dump, analisados no Visual Studio ou no PerfView

Resumo da decisão

  1. Configure o runtime para o limite do container, com 20 a 40% de folga fora do heap.
  2. Todo cache e toda coleção global tem limite.
  3. Ouvintes, temporizadores e tarefas têm um fim definido.
  4. Monitore o heap depois das coletas e os reinícios por falta de memória.
  5. Investigue com duas capturas do heap, comparando o que cresceu.

O próximo nível mostra um cache em memória com limite nas cinco linguagens e as ferramentas de investigação em uso.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Configurar a memória do meu runtime

    Quero configurar corretamente a memória do runtime da minha aplicação para o container onde ela roda.
    
    Pergunte qual linguagem e versão eu uso, qual é o limite de memória do container e que tipo de carga a aplicação tem.
    
    Explique, para o meu caso:
    1. O que o runtime faz por padrão com esse limite.
    2. Qual configuração usar e com que valor, deixando folga para memória fora do heap.
    3. O que acontece se o valor for pequeno demais ou grande demais.
    4. Como confirmar, depois do ajuste, que o coletor não está trabalhando demais nem deixando o processo chegar perto do limite.
    
    Termine com a linha exata de configuração para o meu Dockerfile ou manifesto.
  • Desenhar código que não vaza

    Quero revisar as práticas de código que evitam vazamentos de memória na minha aplicação.
    
    Pergunte qual linguagem e framework eu uso e se a aplicação tem caches, filas internas, ouvintes de eventos ou tarefas em segundo plano.
    
    Para cada um desses elementos, explique:
    1. Como ele pode vazar memória.
    2. A forma correta de implementá-lo na minha linguagem, com código.
    3. Que limite ou cancelamento ele precisa ter.
    
    Explique também como processar arquivos e resultados grandes sem carregar tudo na memória. Termine com uma lista de regras para revisão de código.
  • Plano de monitoramento de memória

    Quero montar o monitoramento de memória da minha aplicação para detectar vazamentos antes que derrubem o serviço.
    
    Pergunte qual linguagem, plataforma de execução e ferramenta de monitoramento eu uso.
    
    Explique:
    1. Quais métricas coletar: memória do processo, heap depois das coletas, frequência e duração das coletas, reinícios por falta de memória.
    2. Como expor as métricas do runtime da minha linguagem.
    3. Como diferenciar crescimento normal de vazamento num gráfico.
    4. Quais alertas configurar, com quais limites.
    5. O que fazer quando o alerta disparar.