Aplicação reiniciando sozinha: erros de falta de memória (OOM) explicados

Um erro de falta de memória (OOM) significa que a aplicação pediu mais RAM do que o limite permite, e o runtime ou o kernel a encerrou. Leia o log, descubra o que cresce e corrija o vazamento ou aumente o limite.

Atualizado em 7 min de leitura

Objetivos de aprendizado

Depois de ler este artigo, você vai conseguir:

  • Diferenciar um erro de OOM do runtime de um encerramento silencioso pelo kernel
  • Descobrir o que está consumindo memória em Node.js, Python ou Java
  • Corrigir a causa e definir limites para o problema não voltar
Nesta página

Sua aplicação roda bem por uma hora, um dia ou uma semana, e de repente some e começa de novo. O código não mudou e não há stack trace. Esse padrão, um reinício sem erro de código, é a marca clássica de um crash por falta de memória.

Memória é o único recurso que não se degrada aos poucos. Falta de CPU deixa a aplicação mais lenta. Falta de memória encerra o processo.

RAMtempolimite de memóriauso estávelvazamentoOOM killreinício
Uso estável não cai. Um vazamento sobe até o limite, o processo é morto e o ciclo recomeça a cada reinício.

O que é um erro de falta de memória (OOM)?#

Todo processo precisa de RAM para o código, os dados e o runtime que o executa. Um container tem um limite de memória. Quando a aplicação tenta usar mais do que esse limite e o sistema não consegue liberar nada, algo tem que ceder.

O kernel do Linux documenta isso para cgroups: se o uso de memória de um cgroup chega ao memory.max e não pode ser reduzido, o OOM killer é acionado naquele cgroup. O Docker diz o mesmo em termos simples: por padrão, se ocorre um erro de OOM, o kernel mata processos dentro do container.

Na prática, o nome "OOM" cobre duas falhas diferentes:

  • OOM do runtime. O runtime da linguagem percebe que acabou o heap e aborta com uma mensagem que você consegue ler no log.
  • OOM kill do kernel. O kernel encerra o processo de fora. A aplicação não consegue capturar isso e não escreve mensagem nenhuma.

Como saber se é um crash por OOM?#

Confira estes sinais, nesta ordem.

1. A mensagem no log. Um OOM do runtime no Node.js tem esta cara:

text
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

O meio da mensagem varia (você também pode ver Ineffective mark-compacts near heap limit), mas ela termina com JavaScript heap out of memory.

O Python lança MemoryError e o Java lança java.lang.OutOfMemoryError (por exemplo Java heap space). Se você vê um desses, é um OOM do runtime, e o stack trace mostra onde aconteceu.

2. Um log que simplesmente para. Num kill do kernel, a última linha é uma saída comum, e a seguinte já é a mensagem de inicialização, sem erro nenhum no meio. Junto com o próximo sinal, isso é quase conclusivo.

3. Código de saída 137. Se suas ferramentas mostram o código de saída, 137 significa 128 mais o sinal 9 (SIGKILL), pela convenção do shell. É o que aparece quando o kernel mata um processo de container.

4. O gráfico de memória. Olhe a RAM ao longo do tempo. Uma linha que sobe e cai para perto de zero a cada reinício indica que a aplicação bateu no limite. Se ainda não sabe onde encontrar essa saída, veja como ler os logs da sua aplicação.

Se o gráfico está estável e baixo na hora do crash, provavelmente não é memória. Procure uma exceção não tratada.

O que faz uma aplicação ficar sem memória?#

Da causa mais comum para a menos comum:

  1. Vazamento de memória. Algo mantém referências que deveria soltar: um array que só cresce, um cache sem limite de tamanho, um event listener adicionado a cada requisição, um map indexado por ID de usuário que nunca é limpo.
  2. Carregar demais de uma vez. Ler um arquivo grande inteiro ou uma tabela inteira do banco para a memória, em vez de usar stream ou paginação.
  3. Um limite simplesmente pequeno demais. A aplicação está saudável, mas o pico normal dela é maior do que a memória recebida. Veja quanta RAM e CPU sua aplicação precisa.
  4. Limite do runtime abaixo do limite do container. O Node.js e a JVM têm tetos próprios de heap, separados do container. A memória fora do heap (buffers, bibliotecas nativas, pilhas de threads) conta para o container mas não para o heap, então o processo pode ser morto com o heap parecendo normal.
  5. Picos de tráfego. Muitas requisições simultâneas, cada uma segurando um objeto grande. Bots em muitos servidores também passam por isso (veja quanta RAM um bot do Discord precisa).

Como descobrir o que está usando a memória?#

Meça antes de chutar. O objetivo é ver quais objetos crescem.

Node.js#

Registre o uso de memória a cada poucos segundos. O rss é o processo inteiro e é o que conta para o limite do container:

js
// memory-log.js: carregue primeiro no seu arquivo de entrada (require ou import)
const mb = (n) => Math.round(n / 1024 / 1024);

setInterval(() => {
  const m = process.memoryUsage();
  console.log(`rss=${mb(m.rss)}MB heapUsed=${mb(m.heapUsed)}MB external=${mb(m.external)}MB`);
}, 30_000).unref();

Se o heapUsed sobe junto com o rss, é um vazamento em JavaScript. Se o rss sobe mas o heapUsed fica estável, olhe para buffers, módulos nativos ou streams grandes.

Para ter um heap snapshot logo antes do crash, inicie o Node com --heapsnapshot-near-heap-limit. A documentação do Node.js descreve que ele grava um heap snapshot do V8 no disco quando o uso do heap se aproxima do limite:

bash
node --max-old-space-size=400 --heapsnapshot-near-heap-limit=2 index.js

Abra o arquivo .heapsnapshot gerado no Chrome DevTools (aba Memory) e ordene por retained size. Tirar snapshots também custa tempo e memória, então use isso para investigar, não como configuração permanente.

Python#

A biblioteca padrão tem o tracemalloc. Inicie cedo, tire um snapshot depois e imprima as linhas que mais alocaram:

python
import tracemalloc

tracemalloc.start()

leaky = [bytes(1000) for _ in range(50_000)]  # faz o papel da sua carga de trabalho real

snapshot = tracemalloc.take_snapshot()
for stat in snapshot.statistics("lineno")[:10]:
    print(stat)

O rastreamento adiciona custo, e um snapshot só cobre as alocações feitas depois do start(), então ligue-o enquanto depura.

Java#

Peça à JVM que grave um heap dump quando lançar OutOfMemoryError. Por padrão isso vem desligado:

bash
java -Xmx384m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./dumps/ -jar app.jar

Abra o arquivo .hprof numa ferramenta como Eclipse MAT ou VisualVM e procure os maiores objetos retidos.

Como corrigir um crash por OOM?#

Ajuste a correção à causa.

  • Vazamento: remova a referência. Limite todo cache (um número máximo de entradas e um tempo de vida), remova os event listeners que você adiciona e pare de guardar dados por usuário para sempre.
  • Dados demais de uma vez: use stream nos arquivos, pagine as consultas e processe em lotes, em vez de montar uma lista gigante.
  • Limite pequeno demais: aumente a memória da aplicação para o pico medido mais 30% a 50% de folga, como explica o artigo de dimensionamento acima.
  • Teto do runtime desalinhado: defina o teto do heap abaixo do limite do container, para que o runtime falhe com um erro legível em vez de um kill silencioso. No Node.js, a documentação sugere, para uma máquina de 2 GiB, definir --max-old-space-size como 1536 para deixar espaço para outros usos. Aplique a mesma proporção ao seu limite. No Java, defina -Xmx explicitamente, já que o padrão é escolhido em tempo de execução a partir da configuração do sistema.
  • Picos: limite a concorrência, adicione uma fila ou leve o trabalho pesado (processamento de imagem, exportações grandes) para um processo separado.

Aumentar o limite só é uma correção válida quando o uso é estável. Se o gráfico sobe para sempre, um limite maior só faz o crash acontecer mais tarde e ficar mais difícil de reproduzir.

Como evitar que volte a acontecer?#

  • Registre a memória ao longo do tempo, como nos trechos acima, e olhe os números depois de cada release.
  • Defina tetos de heap explícitos para que as falhas sejam barulhentas.
  • Faça testes de carga com dados realistas antes de lançar, não com um banco vazio.
  • Crie um alerta para quando a memória ficar acima de uns 80% do limite por muito tempo, usando um monitor externo de uptime ou de métricas.
  • Reinicie em horário fixo só como paliativo enquanto corrige um vazamento conhecido, nunca como a correção.

Como fazer isso na Square Cloud#

Na Square Cloud, cada aplicação roda num container isolado com a própria alocação de memória, e a RAM do plano é um pool que você divide entre suas aplicações. O mínimo é 256 MB para uma aplicação de fundo, como um bot, e 512 MB para uma aplicação web, e uma única aplicação pode usar até 32 GB.

Quando uma aplicação fica sem memória, ela para e continua parada: o reinício automático (ligado por padrão no deploy pelo painel) só cobre um processo que encerra com erro, então você aumenta a RAM e inicia de novo. Os logs em tempo real no painel, na CLI e na extensão do VS Code guardam a saída de antes da parada, então dá para ler as últimas linhas antes do crash. As métricas de CPU, RAM e rede são coletadas a cada 5 minutos e guardadas por 24 horas, o que pode mostrar o desenho de subida e queda de um vazamento que derruba a aplicação dentro de um dia.

Para corrigir um limite pequeno demais, mude a RAM da aplicação nas configurações dela ou pela API. Isso é escala vertical, feita por você: não há autoscaling. Os demais recursos de logs e métricas estão na página de recursos.

Deploy

Envie pelo GitHub, pela CLI ou em ZIP e publique em segundos

Saiba mais

Perguntas frequentes

O que significa o código de saída 137?

Pela convenção do shell, 137 é 128 mais 9, ou seja, o processo foi encerrado por SIGKILL. Num container, esse é o sinal típico de um OOM kill, mas qualquer outro encerramento forçado gera o mesmo código, então confirme no gráfico de memória.

Por que não aparece nenhum erro no log quando a aplicação morre?

Quando o kernel mata um processo por passar do limite de memória, o processo não tem chance de registrar nada. Você só vê o log parar de repente, seguido de um reinício.

Vazamento de memória é a mesma coisa que erro de OOM?

Não. Vazamento é memória que cresce e nunca é liberada. O erro de OOM é o resultado quando o uso chega ao limite. O vazamento é uma das causas, mas uma aplicação também pode bater no limite carregando dados demais de uma vez.

Basta contratar mais RAM?

Se o uso é estável e fica só um pouco acima do limite, mais RAM é a correção certa. Se o uso sobe sem parar até a aplicação cair, mais RAM só adia o crash e você precisa corrigir o vazamento.

Próximo artigoO que é Docker e para que serveDocker é uma plataforma que empacota uma aplicação e suas dependências em uma imagem e a roda como container. Veja como funciona, um exemplo e os limites.