Corrigindo "Cannot allocate memory" (ENOMEM): Overcommit e Swap
O que e "Cannot allocate memory"?
Conclusao: Uma chamada de sistema de alocacao de memoria (
malloc/brk/mmap/fork) foi recusada pelo kernel e retornouENOMEM. O processo nao e encerrado -- apenas a alocacao falha. E diferente do OOM killer, e pode aparecer mesmo quandofreemostra espaco. A causa nem sempre e pouca RAM; geralmente e um "limite" como contabilidade de overcommit,ulimit -voumax_map_count.
Cannot allocate memory e a mensagem padrao para o errno ENOMEM. Aparece em logs de aplicacoes, dmesg ou strace:
$ ./myapp fork: Cannot allocate memory $ python3 -c 'x = bytearray(8*1024**3)' MemoryError $ strace -f ./myapp 2>&1 | grep ENOMEM mmap(NULL, 1073741824, ...) = -1 ENOMEM (Cannot allocate memory)
O ponto chave e que isso se comporta de forma diferente de um kill forcado pelo OOM killer. O OOM killer encerra um processo apos a memoria se esgotar e deixa um log. ENOMEM simplesmente recusa a solicitacao no momento da alocacao, e o processo permanece vivo e recebe um erro. Por isso voce pode ver Cannot allocate memory sem Out of memory: Killed process no dmesg.
| Aspecto | Cannot allocate memory (ENOMEM) | OOM killer |
|---|---|---|
| Quando ocorre | Na chamada de sistema de alocacao | Apos a memoria fisica se esgotar |
| Processo | Sobrevive (recebe um erro) | Encerrado com SIGKILL |
| Log no dmesg | Nenhum | Out of memory: Killed process |
| Causa principal | Contabilidade de overcommit / limites | RAM + swap esgotados |
Premissas (ambiente alvo)
- SO: Ubuntu / Linux geral
- Sintoma:
malloc/fork/mmapfalha comCannot allocate memory - Inclui casos onde
freeparece ter espaco - Voce pode ler
/proc/meminfo/ulimit/sysctl(configuracoes permanentes requeremsudo)
Por que aparece quando a RAM esta livre? (Como funciona o Overcommit)
Conclusao: O Linux contabiliza quanta memoria foi "committed" (prometida) na alocacao. Sob
vm.overcommit_memory=2(modo estrito), no momento em que o commit total excedeCommitLimit, retornaENOMEM. Essa "quantidade total prometida" (Committed_AS) e separada da memoria fisica livre, entao a alocacao pode ser recusada mesmo com RAM sobrando.
Quando um processo aloca memoria, paginas reais sao mapeadas apenas na primeira escrita (demand paging). O que o kernel rastreia no momento da alocacao e a "quantidade prometida para uso futuro" -- o commit. vm.overcommit_memory decide ate onde esse commit e permitido, em tres modos:
| Modo | Valor | Comportamento |
|---|---|---|
| 0 | padrao | Heuristica. Recusa apenas alocacoes obviamente excessivas |
| 1 | - | Sempre permite. malloc quase nunca retorna NULL (OOM pode disparar depois) |
| 2 | - | Contabilidade estrita. ENOMEM quando commit total excede CommitLimit |
O limite no modo estrito (2) e visivel em /proc/meminfo:
$ grep -i commit /proc/meminfo
CommitLimit: 6029308 kB Committed_AS: 5980124 kB
CommitLimit e o "total que voce pode prometer", e Committed_AS e "o que esta prometido atualmente". Conforme o segundo se aproxima do primeiro, novas alocacoes sao rejeitadas com ENOMEM. CommitLimit e calculado como:
CommitLimit = swap total + RAM fisica x (vm.overcommit_ratio / 100)
O vm.overcommit_ratio padrao e 50. Entao no modo 2, por padrao voce so pode prometer "swap + metade da RAM fisica". Mesmo com RAM livre, uma vez que esse limite contabil e atingido, a alocacao falha -- o classico padrao "livre mas ainda falha".
Mesmo no modo 0 (padrao), uma alocacao individual excessiva (ex: um mmap maior que RAM + swap) e recusada pela heuristica. Quando voce ve Cannot allocate memory, primeiro verifique em qual modo esta com cat /proc/sys/vm/overcommit_memory.
O que verificar primeiro? (Por onde comecar)
Conclusao: Verifique, em ordem: (1) qual operacao falhou (
forkvsmalloc/mmap), (2) sedmesgmostra OOM, (3) espaco real emfree -h, (4) espaco contabil emgrep -i commit /proc/meminfo, e (5) o limite do processo emulimit -v. Esses cinco pontos determinam quase completamente se e exaustao real, contabilidade de overcommit ou limite de processo.
A primeira divisao e "a memoria realmente acabou, ou a solicitacao foi recusada por um limite?"
# 1. Algum sinal de que o OOM killer rodou (se sim, e o lado de exaustao real) $ dmesg -T | grep -i -E 'out of memory|killed process' # 2. Espaco real de memoria fisica / swap $ free -h # 3. Espaco contabil de overcommit (importante no modo 2) $ grep -i -E 'commitlimit|committed_as' /proc/meminfo # 4. Limite de memoria virtual deste shell / processo $ ulimit -v
Tabela de decisao rapida:
| Observacao | Causa suspeita | Va para |
|---|---|---|
free available pequeno / log OOM presente |
Exaustao real de memoria | Adicionar swap / reduzir uso |
Modo 2 e Committed_AS ~ CommitLimit |
Limite contabil overcommit | Ajustar overcommit |
ulimit -v nao e unlimited |
Limite de mem virtual do processo | ulimit / cgroup |
Apenas mmap falha / processo com muitas areas |
max_map_count atingido |
max_map_count |
Leia a coluna available de free (nao a coluna free). available e a memoria livre realista apos recuperar cache e e o sinal principal de exaustao real. Veja investigando pressao de memoria para detalhes.
A configuracao de overcommit e a causa? (vm.overcommit_memory)
Conclusao: Se o modo e 2 e
Committed_ASesta grudado noCommitLimit, a contabilidade de overcommit e a culpada. Aumentarvm.overcommit_ratioou adicionar swap ampliaCommitLimit. Mas quanto mais flexivel a contabilidade, maior o risco real de OOM, entao a correcao raiz e revisar o uso de memoria.
Verifique o modo e ratio atuais.
$ cat /proc/sys/vm/overcommit_memory $ cat /proc/sys/vm/overcommit_ratio
2 50
Ha duas formas de ampliar CommitLimit: aumentar o ratio ou adicionar swap.
# Aumentar o ratio para 80% (permitir prometer RAM x 80% + swap) $ sudo sysctl -w vm.overcommit_ratio=80 $ grep -i commitlimit /proc/meminfo # confirmar aplicacao
Tornar permanente.
$ echo 'vm.overcommit_ratio = 80' | sudo tee /etc/sysctl.d/99-overcommit.conf $ sudo sysctl --system
O modo 2 e deliberadamente usado onde voce "nunca quer que o OOM killer rode" (bancos de dados, embarcados, etc.). Aumentar o ratio cegamente enfraquece essa protecao de contabilidade estrita. Primeiro descubra por que Committed_AS esta grande (reservas de heap excessivas, processos demais); so se ainda faltar espaco, ajuste o ratio ou adicione swap.
Inversamente, se seu unico problema e "malloc retorna NULL", o modo 1 (sempre permitir) e uma opcao, mas as alocacoes so tem sucesso para enfrentar o OOM killer na escrita. O formato da falha apenas muda de "Cannot allocate memory" para "kill subito", entao evite mudancas casuais. Para comportamento do OOM, veja tratando eventos do OOM killer.
E um limite de processo ou usuario? (ulimit -v / cgroup)
Conclusao: Se
ulimit -v(RLIMIT_AS) nao eunlimited, o espaco de endereco virtual desse processo esta limitado e retornaENOMEM. Sob systemd,MemoryMax/ limites de cgroup cortam a alocacao da mesma forma. O sistema como um todo pode ter bastante, mas o limite por processo e o que falha.
Verifique tanto o shell logado quanto o processo em execucao.
# Limite de memoria virtual do shell atual (KB; unlimited significa sem limite) $ ulimit -v # Verificar o limite de um processo em execucao diretamente (por PID) $ cat /proc/<pid>/limits | grep -i 'address space'
Max address space 2147483648 2147483648 bytes
Se ulimit -v e menor do que a aplicacao precisa, essa e a causa direta. Se roda como servico systemd, verifique tambem os limites da unit.
$ systemctl show -p MemoryMax -p LimitAS myapp.service
Para dar mais memoria ao servico, configure um drop-in.
$ sudo systemctl edit myapp.service
[Service] MemoryMax=4G LimitAS=infinity
$ sudo systemctl daemon-reload $ sudo systemctl restart myapp.service
Se voce limita ulimit -v no .bashrc ou similar, cada processo lancado desse shell herda o limite. Limites de container (Docker --memory) e limites de cgroup causam ENOMEM da mesma forma. Se "so acontece para um certo usuario ou servico", suspeite do limite herdado primeiro.
E se mmap retorna ENOMEM? (vm.max_map_count)
Conclusao: Se a memoria tem espaco mas apenas
mmapfalha comCannot allocate memory, voce provavelmente atingiuvm.max_map_count(padrao 65530), o limite de quantas areas de mapa de memoria um processo pode manter. Isso acontece com Elasticsearch, muitas threads ou carregamento de muitas bibliotecas compartilhadas. Aumente o limite para corrigir.
Confirme que mmap e a causa com strace.
$ strace -f -e trace=mmap ./myapp 2>&1 | grep ENOMEM
mmap(NULL, 262144, PROT_READ|PROT_WRITE, ...) = -1 ENOMEM (Cannot allocate memory)
Compare a contagem atual de mapas do processo com o limite.
# Numero atual de areas de mapa $ wc -l < /proc/<pid>/maps # Limite do sistema $ sysctl vm.max_map_count
65530 65530
Se a contagem esta grudada no limite, aumente.
$ sudo sysctl -w vm.max_map_count=262144 $ echo 'vm.max_map_count = 262144' | sudo tee /etc/sysctl.d/99-max-map-count.conf $ sudo sysctl --system
262144 e o valor representativo que o Elasticsearch oficialmente requer. O valor certo depende da carga de trabalho, entao primeiro observe como a contagem de mapas (contagem de linhas de /proc/<pid>/maps) cresce e deixe margem acima. Aumenta-lo custa pouca memoria e tem efeitos colaterais minimos.
E se fork e o que falha?
Conclusao:
fork: Cannot allocate memoryacontece quando o filho precisa de uma reserva de commit tao grande quanto o pai, e a contabilidade de overcommit (modo 2) ou escassez real nao consegue reservar. Fork a partir de um processo grande e o gatilho. Mude paraposix_spawn/vfork, ou revise overcommit e swap.
fork compartilha memoria copy-on-write, mas para contabilidade tenta reservar "um commit para as paginas graváveis do pai" em nome do filho. Se o pai ocupa varios GB, o commit pode exceder CommitLimit naquele instante e retornar ENOMEM.
$ ./big_parent fork: Cannot allocate memory
Ha tres direcoes para corrigir:
- Design: Nao faca
fork+execdiretamente de um processo enorme; inicie filhos viaposix_spawnou um processo auxiliar pequeno - Contabilidade: Se overcommit esta no modo 2, aumente o ratio / adicione swap para ampliar
CommitLimit - Flexibilizar:
vm.overcommit_memory=1(sempre permitir) remove a verificacao de reserva (um trade-off contra risco de OOM)
Para o mesmo fork, se o erro e Resource temporarily unavailable (EAGAIN) a causa e o limite de contagem de processos / threads, nao memoria, e a correcao e totalmente diferente. Para problemas de ulimit -u / TasksMax / pid_max, veja corrigindo "fork: Resource temporarily unavailable". Separe seu caminho pela mensagem final (Cannot allocate memory vs Resource temporarily unavailable).
Como corrigir de vez? (Checklist)
Conclusao:
Cannot allocate memoryse divide em duas familias: "escassez real" e "recusado por um limite". Separe-as pela existencia de log OOM, depois percorra contabilidade de overcommit (CommitLimit/Committed_AS) -> limites de processo (ulimit -v/cgroup) ->max_map_count. Flexibilizar contabilidade ou limites e sintomatico; a raiz e o equilibrio com o uso de memoria.
- [ ] Verificou
dmesgem busca de logs do OOM killer (se presente, familia de exaustao real)? - [ ] Verificou espaco real em
free -havailable? - [ ] Verificou o modo atual com
cat /proc/sys/vm/overcommit_memory? - [ ] No modo 2, verificou se
Committed_ASesta proximo deCommitLimit? - [ ] Verificou o limite de espaco de endereco em
ulimit -v//proc/<pid>/limits? - [ ] Verificou
MemoryMax/LimitASno systemd / cgroup / container? - [ ] Para falhas de
mmap, comparouvm.max_map_countcom a contagem de mapas? - [ ] Para falhas de
fork, diferenciouENOMEMdeEAGAIN? - [ ] Antes de flexibilizar (ratio / swap / limites), investigou a causa real do alto uso de memoria?