O artigo explica como o oom killer linux intervém em caso de escassez aguda de memória e por que motivo encerra serviços de forma forçada para manter o servidor operacional. Vou mostrar, passo a passo, como identifico os fatores desencadeantes, compreendo o sistema de pontuação e controlo o comportamento em situações de pressão de memória através de configurações específicas.
Pontos centrais
- Gatilho: Os eventos OOM ocorrem quando a RAM está cheia, o espaço de swap está esgotado e as tentativas de recuperação falham.
- Pontuação: O kernel atribui um
oom_scoree encerra os processos que ocupam uma grande parte da memória. - Reconhecimento: Encontram-se indicações em
dmesgcom „Out of memory“ e „Killed process“. - Sistema de controloCom
oom_score_adjPriorizo os serviços de forma específica, consoante a sua importância. - Prevenção: A monitorização, os limites, a estratégia de troca e a análise de fugas evitam mortes brutais.
O que é o OOM Killer no kernel do Linux?
O OOM Killer é o último Linha de segurança do kernel e encerra processos quando já não há mais páginas de memória disponíveis. Considero-o como um „desligamento de emergência“ controlado, que evita uma falha total e liberta imediatamente memória RAM. Antes disso, o sistema tenta transferir páginas para o disco ou esvaziar as caches, mas, se a pressão persistir, resta apenas o corte drástico. Nos registos, reconheço este momento por entradas com „Out of memory“ e «Killed process», frequentemente acompanhadas por um SIGKILL. Este comportamento está ativado por predefinição e é executado automaticamente, o que muitas vezes causa surpresas em ambientes de produção, quando serviços importantes desaparecem sem aviso prévio.
Quando é que o mecanismo entra em ação?
Os eventos OOM ocorrem quando a memória RAM física está quase totalmente ocupada, a área de swap já não dispõe de espaço disponível e os pedidos de novas páginas falham. Nesta situação, o kernel avalia se é possível recuperar espaço a partir do Cache de página e o swapping ainda ajuda, só ativando o «killer» quando a situação se torna desesperada. Picos de curta duração não desencadeiam diretamente o mecanismo; é necessária uma pressão contínua e tentativas infrutíferas de libertação de memória. Para a análise, ajuda-me dar uma olhadela no RSS, na utilização da swap, na percentagem do cache de páginas e nas alocações por grupo de controlo. Quem quiser compreender mais a fundo como funciona a substituição do cache pode consultar os antecedentes sobre Expulsão da cache de páginas visualizar e medir os efeitos no seu próprio sistema.
Como é que o kernel seleciona a „vítima“?
A seleção segue um Pontuação, que o Linux como oom_score calculado por processo. Uma elevada percentagem da memória total, muito RSS e uma função não crítica resultam numa pontuação mais elevada. Os processos próximos do sistema, como o init, recebem uma dedução, enquanto os processos de trabalho que consomem muita memória ou as caches ficam frequentemente à frente. Através de oom_score_adj posso alterar a pontuação de forma seletiva e, assim, controlar a cadeia de vítimas. Normalmente, o kernel encerra o processo com a pontuação mais elevada através de um SIGKILL, para libertar o máximo de RAM possível de uma só vez.
Identificar indícios: registos e sinais
Após o fim repentino do turno, a primeira coisa que faço é verificar dmesg e os registos do kernel. Se aparecerem mensagens como „Out of memory“ e „Killed process“, anoto o PID, o nome do processo, o utilizador e a pontuação calculada. Muitas vezes, falta um registo de erros na própria aplicação, porque o SIGKILL não permite uma fase de limpeza. Comparo o momento com os dados de monitorização para acompanhar o aumento da RAM, do swap e do RSS por processo. Desta forma, identifico fugas de memória, pilhas demasiado grandes ou limites em falta de forma fiável e rápida.
Assumir o controlo com oom_score e oom_score_adj
Cada processo possui em /proc/[PID]/oom_score um atual Valor devido à sua situação de risco. Com /proc/[PID]/oom_score_adj reduzo ou aumento a probabilidade de o kernel encerrar esse processo. Protejo serviços críticos, como bases de dados, com um Adj negativo; e torno os processos de trabalho menos importantes „sacrificáveis“ com um Adj positivo. A alteração tem efeito imediato, o que é útil especialmente durante implementações ou testes de carga. É assim que transformo um mecanismo de emergência imprevisível numa ferramenta que segue as minhas prioridades.
Cenários típicos de alojamento com limitações de espaço de armazenamento
Em ambientes com muitos contentores, bases de dados e caches, vejo o OOM Killer com especial frequência. Uma base de dados que cresce de forma descontrolada expulsa outros serviços do RAM e resulta em falhas graves. As fugas de memória nas aplicações web vão acumulando pressão ao longo de horas, até que nem mesmo a recuperação de memória consiga resolver a situação. Limites de contentores demasiado generosos num host com recursos insuficientes agravam ainda mais a situação. Quem conhece estes padrões define alertas atempadamente e intervém antes que o «assassino» assuma o controlo.
Boas práticas para evitar mortes violentas
Planeio a memória de forma realista, prevejo uma margem para picos de carga e defino limites claros por serviço. A monitorização regista, por processo, o RSS, a utilização da memória de swap e oom_score, para que os alertas sejam emitidos antes de ocorrer uma situação de emergência. Em configurações de contêineres, defino limites de Cgroup para que os serviços individuais não sobrecarreguem o host. Uma estratégia de swap adequada absorve os picos de carga sem abrandar o sistema de forma permanente. Para uma compreensão e planeamento mais aprofundados, recorro a guias práticos sobre gerir a memória virtual, para que as cargas de trabalho tenham espaço suficiente de forma fiável.
Procedimento estruturado em caso de uma ocorrência de OOM
Após o incidente, a primeira coisa que faço é recolher as entradas do registo dmesg e o ficheiro «kern.log» e organizo-os cronologicamente. No monitorização, analiso as curvas da RAM, do swap, do RSS e da cache de páginas para observar a evolução da carga. Em seguida, verifico os limites do «ulimits», os limites dos Cgroups e dos contentores, bem como os parâmetros das aplicações, como as pilhas das JVMs. Por fim, ajusto oom_score_adj para que o que é mais importante permaneça e o que é supérfluo seja eliminado primeiro. Por fim, resolvo a causa: corrijo as fugas, limito as caches, reduzo o paralelismo e dimensiono corretamente as capacidades.
Particularidades em ambientes VPS e na nuvem
Nas máquinas virtuais, há um segundo nível de limites, por exemplo, devido ao hipervisor ou à orquestração. Por isso, conheço a RAM-Defina a quantidade com exatidão e ajuste os limites do Kubernetes ou dos contentores de forma adequada. O OOM Killer do Linux continua a funcionar conforme descrito, mas os mecanismos do fornecedor podem desencadear restrições adicionais. Especialmente quando há muitos pods, uma priorização clara é útil: as implementações importantes recebem reservas, enquanto as tarefas não críticas são executadas com restrições mais rigorosas. A documentação do fornecedor da plataforma e os testes próprios evitam surpresas durante a operação em produção.
Ajustar com precisão a memória: overcommit, swappiness e caches
Quem quiser controlar os riscos de OOM deve ajustar os parâmetros do kernel com cuidado e compreender a sua interação. vm.overcommit_memory e vm.overcommit_ratio controlar até que ponto o Linux permite alocações virtuais generosas, enquanto vm.swappiness influencia a relação entre o «swapping» e o «reclaim». vm.vfs_cache_pressure controla o nível de agressividade na libertação das caches de inodes e dentries, influenciando assim diretamente a margem de manobra da cache de páginas. Verifico sempre os efeitos em condições realistas Carga, regista as métricas e faz alterações apenas de forma gradual. Para conhecer os contextos e cenários, é útil consultar Excesso de alocação de memória, para escolher as suas próprias predefinições de forma adequada.
| Parâmetro/Indicador | Função no sistema | Onde verificar | Direção habitual |
|---|---|---|---|
| RAM grátis | Proteção contra mortes difíceis | free, /proc/meminfo | Manter reservas suficientes |
| Utilização da área de troca | Amortecedores para picos | free, vmstat | Baixa a moderada |
| vm.overcommit_memory | Atribuição virtual | sysctl | 0/2, consoante o risco |
| vm.overcommit_ratio | Quota para overcommit | sysctl | Adequado à carga de trabalho |
| vm.swappiness | Tendência para o swap | sysctl | Valor médio em vez de valor extremo |
| vm.vfs_cache_pressure | Recuperação das caches do VFS | sysctl | 100 como ponto de partida |
OOM global vs. OOM do cgroup: o que é que morre, exatamente?
Em configurações modernas com Cgroups (v1/v2), um evento OOM pode local num Memory-cgroup ou Mundial ser executado no anfitrião. Se um processo estiver a ser executado num contentor com memória.max (ou limite), o kernel normalmente encerra apenas os processos desse Cgroup („memcg OOM“), enquanto o sistema como um todo continua a funcionar. Em dmesg Percebo isso por indícios como constraint=CONSTRAINT_MEMCG ou referências ao Cgroup em questão. Só quando já não houver nenhum Cgroup que possa ceder mais RAM e a memória global estiver esgotada é que o a nível do sistema OOM Killer. Para garantir a estabilidade, considero importante definir limites de forma a que um serviço que exceda os limites falhe no seu próprio Cgroup, em vez de afetar todo o host. Nos Cgroups v2, posso ainda utilizar memória.alta definir restrições suaves e com memory.oom.group definir que, em caso de emergência, todo o grupo seja encerrado – o que é mais claro do que um processo residual que continua a funcionar parcialmente.
Ferramentas e métricas na prática
Para uma análise rápida das causas, recolho dados quantitativos reproduzíveis. Estas ferramentas ajudam-me regularmente:
- Visão geral do processo:
ps -eo pid,ppid,cmd,%mem,rss --sort=-rss | headindica os programas que consomem mais memória. - Smaps‑Rollup:
cat /proc//smaps_rollupfornece os valores RSS/PSS/Swap de um processo sem necessidade de uma análise demorada. - pmap:
pmap -x | sort -nrk3 | headenumera mapeamentos com tamanho e RSS, ideal para pilhas e segmentos grandes. - Utilização de lajes:
slabtop -omostra as caches do kernel, que podem aumentar significativamente em condições de carga elevada. - Pressão do sistema:
vmstat 1esar -r 1fornecem contexto sobre paging, I/O de swap e libertações de memória. - Estatísticas do cgroup: Na v2, verifico
/sys/fs/cgroup/memory.current,memory.swap.currentememory.statdo serviço em questão.
Ler facilmente os registos OOM do #
dmesg -T | egrep -i 'out of memory|oom-kill|killed process'
# Ordenar os principais candidatos por oom_score
for p in /proc/[0-9]*; do
pid=${p##*/}
[ -r "$p/oom_score" ] || continue
printf "%6s %5s %-30s\n" \
"$(cat $p/oom_score)" \
"$(cat $p/oom_score_adj 2>/dev/null || echo 0)" \
"$(tr -d '\0' < $p/comm)"
done | sort -nr | head -n 20
Quando vejo repetidamente erros OOM, registo os Linha de base Estes valores em condições normais de funcionamento e compare-os com os dados da janela de incidentes. Os desvios saltam imediatamente à vista, como, por exemplo, um aumento descontrolado do PSS ou «slabs» desproporcionalmente grandes.
Systemd, contentores e orquestração: controlo direcionado
No systemd, defino as prioridades e os limites declarado nos ficheiros Unit:
[Serviço]
# Proteger o processo ou torná-lo sacrificável
OOMScoreAdjust=-900
# Limites de memória rígidos/flexíveis (cgroup v2)
MemoryMax=8G
MemoryHigh=6G
# Opcional: Limitar a área de troca
MemorySwapMax=2G
# Comportamento em caso de OOM no systemd
# (por exemplo, forçar o reinício)
Restart=on-failure
RestartSec=5
Em ambientes de contentores, garanto que existam limites claros para cada serviço. Para mim, é importante distinguir entre Pedido (reserva prevista) e Limite (limite máximo rígido). Os pods com pedidos/limites adequados recebem melhores classificações de QoS; as cargas de trabalho „BestEffort“ estão sujeitas a OOM. Um pormenor prático: se o kernel encerrar um contentor devido a um Cgroup OOM, vejo frequentemente o código de saída 137 e acontecimentos com OOMKilled; no servidor‑dmesg É possível estabelecer essa correlação. Em clusters produtivos, planeio as implementações críticas como „Guaranteed“, enquanto os trabalhos em lote são executados deliberadamente com intervalos mais curtos e, assim, cedem prioridade.
Detalhes do kernel: OOM Reaper, THP e fragmentação
Após a morte, o OOM Reaper: um thread do kernel retira o mapeamento de memória do processo da vítima o mais rapidamente possível, para que a RAM fique efetivamente livre. Isso explica por que razão, por vezes, a memória só para a entrada de Killeintrag volta a ficar visível. Paralelamente, a Compactação da memória atingir os limites – se a RAM estiver muito fragmentada, faltam áreas contínuas para grandes alocações (por exemplo, com Transparent Huge Pages, THP). O THP proporciona desempenho, mas pode dificultar as alocações quando está sob pressão. Para cargas de trabalho em que a latência é crítica, desativo ou limito o THP a título experimental e avalio os efeitos.
Outro fator a ter em conta é Caches de slab e a cache de páginas: em cargas de trabalho com elevada carga de E/S, estas caches aumentam significativamente. Com vm.vfs_cache_pressure e, através de uma recuperação seletiva, é possível controlar a sua proporção; o esvaziamento generalizado (Drop-Caches) utilizo-o, no máximo, como instrumento de diagnóstico, não como solução permanente. Além disso, presto atenção a NUMA: Se um nó de memória estiver esgotado, um processo nessa zona NUMA pode falhar, apesar de haver RAM livre a nível global. Também aparecem mensagens a esse respeito nos registos do kernel.
Aprofundar as estratégias de swap: Swappiness, ZRAM/Zswap, orçamento de E/S
O swap não é um mal, mas sim um Amortecedor. O importante é utilizá-lo de forma inteligente. Com vm.swappiness ajusto a que ponto o kernel recorre à área de troca. Valores demasiado baixos fazem com que a cache de páginas domine e podem desencadear erros de memória insuficiente (OOM) mais cedo; valores demasiado altos transferem a carga para as operações de E/S da área de troca e tornam o sistema lento. Em hosts compactos, costumo utilizar ZRAM ou Zswap, para criar uma memória tampão comprimida que absorva os picos sem sobrecarregar o disco. É importante ter em conta que a memória swap não substitui a capacidade em falta. Apenas ganha tempo para que o OOM Killer nem sequer tenha de ser acionado.
Casos especiais: mlock, RLIMITS, armadilhas do overcommit
Algumas condições de contorno aumentam os riscos de OOM ou alteram o comportamento:
- Memória bloqueada: Processos que, através de
mlock()Ao fixar páginas, estas são retiradas do Reclaim. Se a taxa for elevada, o ritmo do Reaper pode ser abrandado. - RLimits:
RLIMIT_ASeRLIMIT_RSSestabelecem limites máximos por processo e impedem que determinados serviços se tornem excessivos – um elemento fundamental na prevenção de erros OOM. - Compromisso excessivo: Configurações de overcommit demasiado generosas permitem um grande espaço de endereçamento virtual que, posteriormente, não pode ser coberto fisicamente. Os picos de alocação de muitos threads, em particular, provocam erros de acesso e aceleram os eventos OOM.
- panic_on_oom: No caso de sistemas altamente críticos, existe a opção de reagir a uma situação de OOM com um kernel panic. Isso só faz sentido em cenários de alta disponibilidade (HA) bem definidos; caso contrário, é contraproducente.
- „Unkillbar“ é arriscado:
oom_score_adj=-1000Embora proteja contra o «killer», pode bloquear todo o sistema. Só o utilizo para processos pequenos e absolutamente essenciais (por exemplo, o init), e não para serviços de servidor que consomem muita memória.
Na prática: definir prioridades e garantir as alterações
Defino na equipa uma Classificação dos serviços: O que deve permanecer e o que pode ser eliminado em primeiro lugar? Traduzo esta ordem em oom_score_adj, limites de Cgroup e (quando disponíveis) políticas de reinício. As alterações são incorporadas como código nos manifestos de unidade ou de implementação, acompanhadas de pontos de medição no sistema de monitorização. Nos testes de carga, simulo pressão na memória: aumente os volumes de dados, aumente o paralelismo, deixe as caches crescerem – e observo se são precisamente os processos „sacrificáveis“ que caem, enquanto os componentes essenciais permanecem online. Só quando isto funcionar de forma reprodutível é que a configuração entra em produção.
Padrões de diagnóstico: distinguir entre fugas, pilhas e fragmentação
Nem todo aumento no RSS é uma fuga de informação. Faço uma distinção sistemática:
- Fuga: Os valores de RSS/PSS aumentam de forma monótona, mesmo sem aumento da carga;
smaps_rollupcresce de forma uniforme; os ciclos GC (nos Managed Runtimes) não ajudam. - Pontas de heap: O RSS aumenta com o aumento da carga e volta a diminuir; a cache de páginas está correlacionada com os padrões de E/S.
- Fragmentação: Há memória RAM livre suficiente, mas as alocações de grande dimensão falham; os registos mostram tentativas de compactação, e as alocações de THP falham com maior frequência.
Para cargas de trabalho JVM ou Node, verifico se o ambiente de execução reconhece os limites dos contentores. Heaps ou caches de código JIT com dimensões excessivas podem ultrapassar os limites previstos e provocar erros de memória insuficiente (OOM), mesmo que aparentemente ainda haja margem disponível. Defino os heaps, incluindo a sobrecarga, de forma a que, em MemóriaMax ainda reste espaço para as partes nativas, as pilhas de threads e a cache de páginas.
Guia prático para implementações e testes de carga em condições de pressão de armazenamento
- Medir o valor de referência: RSS/PSS por serviço, percentagens de slab, rácio de swap, tamanhos de cache,
oom_score. - Estabelecer limites: Memória Alta/Máxima ou definir limites para os contentores com uma margem de segurança realista;
OOMScoreAdjustatribuídos por ordem de prioridade. - Criar stress: Volume de dados, concorrência, crescimento da cache; registar os perfis de E/S e da CPU.
- Observar:
dmesg -T, métricas do host e do cgroup; verificar quem é o primeiro a ficar sob pressão. - Iterar: Ajustar os limites/Adj, ajustar o Swappiness, testar as definições do THP, medir novamente.
- Automatizar: Integrar verificações no CI/CD, alertas em caso de valores limite, políticas de reinício para serviços afetados.
Em resumo: medidas concretas
Entendo o OOM Killer como Sinal, que o meu sistema tinha anteriormente pouca margem de manobra ou que os processos estavam mal priorizados. Com monitorização, limites realistas, uma estratégia de swap bem definida e uma utilização consciente de oom_score_adj Reduzo significativamente os encerramentos forçados. Em configurações produtivas, protejo os processos essenciais, torno os serviços secundários dispensáveis e avalio cada alteração. No caso dos contentores, defino limites de Cgroup de forma rigorosa, para que um serviço não bloqueie todo o sistema anfitrião. Quem mantiver esta disciplina mantém o Linux responsivo mesmo sob pressão e reduz significativamente o tempo necessário para identificar a causa.


