19 out 2026 · linux, memoria, diagnostico · 6 min
Como ver o que está comendo a memória no Linux
Some o RSS de todos os processos de uma máquina de 5,9 GB e dá 4,8 GB. Some o PSS dos mesmos processos e dá 3,8 GB. Nenhum dos dois números é mentira: são o mesmo consumo contado de duas maneiras, e a diferença de 959 MB é a mesma página de biblioteca carregada por vários processos ao mesmo tempo.
É por isso que a soma nunca bate com o que o free mostra como usado, e por isso que a lista de processos parece maior que a RAM do computador. O script abaixo faz as três contas ao mesmo tempo, para a diferença aparecer com nome e sobrenome.
O que cada número quer dizer
RSS é a memória residente do processo: tudo que ele tocou e o kernel não despejou. O problema é que página compartilhada entra inteira na conta de cada processo que a carregou. Dez processos Node com a mesma libc contabilizam dez vezes a mesma biblioteca.
PSS, proportional set size, divide cada página compartilhada entre os processos que a compartilham. Dez processos com a mesma libc contam a biblioteca uma vez, dividida em dez. É a única conta que permite comparar processos.
Cache de página é outra coisa. O kernel usa memória sobrante para guardar arquivo lido recentemente e a devolve quando precisa. No /proc/meminfo isso aparece em Cached e SReclaimable, e não pertence a processo nenhum.
Quem decide se a máquina está apertada é o MemAvailable, que já desconta o cache recuperável.
O artefato
Um script bash que não instala nada, não pede root e lê /proc direto. Ele agrupa por nome de processo, o que resolve o caso de servidor com doze workers aparecendo como doze linhas.
#!/usr/bin/env bash
# memoria.sh - mostra o que esta comendo a RAM, sem confundir as tres contas.
#
# Por que existe: a soma do RSS de todos os processos nunca bate com o que o
# free chama de "usado", e quase sempre parece que falta memoria. Faltam duas
# contas no meio do caminho: paginas compartilhadas (a mesma libc contada em
# todos os processos que a carregaram) e cache de pagina, que o kernel devolve
# sozinho sob pressao. Aqui as duas ficam separadas.
#
# Uso:
# ./memoria.sh top 12 processos
# ./memoria.sh 25 top 25
#
# Nao precisa de root e nao instala nada: le /proc direto.
set -uo pipefail
TOP="${1:-12}"
# --- 1. A conta do kernel, lida de /proc/meminfo -------------------------
# MemAvailable e o numero que decide se a maquina esta apertada. Cache e
# reclaimavel, entao nao conta como problema.
declare -A MEM=()
while read -r chave valor _; do
case "$chave" in
MemTotal:|MemAvailable:|Buffers:|Cached:|SReclaimable:|SwapTotal:|SwapFree:)
MEM["${chave%:}]"]="${valor:-0}" ;;
esac
done < /proc/meminfo
mb() { awk -v k="${1:-0}" 'BEGIN{printf "%d", k/1024}'; }
echo "memoria.sh - $(uname -sr), ${TOP} maiores por PSS"
echo
printf ' total %6s MB\n' "$(mb "${MEM[MemTotal]:-0}")"
printf ' disponivel %6s MB\n' "$(mb "${MEM[MemAvailable]:-0}")"
printf ' cache %6s MB (buffers + cached + reclaimavel)\n' \
"$(mb $(( ${MEM[Buffers]:-0} + ${MEM[Cached]:-0} + ${MEM[SReclaimable]:-0} )))"
if [ "${MEM[SwapTotal]:-0}" -gt 0 ]; then
printf ' swap %6s MB de %6s MB\n' \
"$(mb $(( ${MEM[SwapTotal]:-0} - ${MEM[SwapFree]:-0} )) )" \
"$(mb "${MEM[SwapTotal]:-0}")"
fi
echo
# --- 2. Cada processo, medido uma vez -------------------------------------
# PSS (proportional set size) e a unica conta honesta para comparar
# processos: cada pagina compartilhada e dividida entre quem a compartilha.
# RSS soma a pagina inteira em cada processo e infla o total.
# smaps_rollup existe desde o kernel 4.14; sem ele o script cai para VmRSS e
# avisa no fim.
if [ -r /proc/self/smaps_rollup ]; then TEM_SMAPS=1; else TEM_SMAPS=0; fi
BRUTO="$(mktemp 2>/dev/null || echo "./.memoria-bruto.$$")"
trap 'rm -f "$BRUTO"' EXIT
for dir in /proc/[0-9]*; do
[ -r "$dir/status" ] || continue
nome="$(awk '/^Name:/{print $2; exit}' "$dir/status" 2>/dev/null)"
[ -n "$nome" ] || nome="?"
rss=0; pss=0; priv=0; swap=0
if [ "$TEM_SMAPS" -eq 1 ] && [ -r "$dir/smaps_rollup" ]; then
rss="$(awk '/^Rss:/{print $2; exit}' "$dir/smaps_rollup" 2>/dev/null)"
pss="$(awk '/^Pss:/{print $2; exit}' "$dir/smaps_rollup" 2>/dev/null)"
priv="$(awk '/^Private_Dirty:/{print $2; exit}' "$dir/smaps_rollup" 2>/dev/null)"
swap="$(awk '/^Swap:/{print $2; exit}' "$dir/smaps_rollup" 2>/dev/null)"
fi
if [ "${rss:-0}" -eq 0 ] && [ "${pss:-0}" -eq 0 ]; then
rss="$(awk '/^VmRSS:/{print $2; exit}' "$dir/status" 2>/dev/null)"
swap="$(awk '/^VmSwap:/{print $2; exit}' "$dir/status" 2>/dev/null)"
if [ "$TEM_SMAPS" -eq 1 ] && [ -r "$dir/smaps_rollup" ]; then
pss="$(awk '/^Pss:/{print $2; exit}' "$dir/smaps_rollup" 2>/dev/null)"
else
pss="$rss"
fi
fi
[ "${rss:-0}" -gt 0 ] || continue
printf '%s\t%s\t%s\t%s\t%s\n' "${pss:-$rss}" "$rss" "${priv:-0}" "${swap:-0}" "$nome" >> "$BRUTO"
done
# --- 3. Top por PSS, agrupando processos de mesmo nome --------------------
# nginx worker 12 e nginx worker 13 aparecem somados, que e como o servico
# realmente consome.
awk -F'\t' -v top="$TOP" '
{ pss[$5]+=$1; rss[$5]+=$2; priv[$5]+=$3; swp[$5]+=$4; n[$5]++ }
END { for (k in pss) printf "%012d\t%s\t%d\t%d\t%d\t%d\t%d\n", pss[k], k, pss[k]/1024, rss[k]/1024, priv[k]/1024, swp[k]/1024, n[k] }
' "$BRUTO" | sort -rn | head -n "$TOP" | awk -F'\t' '
BEGIN { printf "%-16s %7s %7s %8s %6s %4s\n", "processo", "PSS", "RSS", "privado", "swap", "n" }
{ printf "%-16s %6dM %6dM %7dM %5dM %4d\n", $2, $3, $4, $5, $6, $7 }
'
# --- 4. A conta que fecha -------------------------------------------------
# Soma de RSS menos soma de PSS e a mesma memoria contada duas ou mais vezes.
# E o que faz a lista parecer maior que a RAM do computador.
echo
awk -F'\t' '{ pss+=$1; rss+=$2; priv+=$3 } END {
printf " soma dos PSS de todos %6d MB\n", pss/1024
printf " soma dos RSS de todos %6d MB\n", rss/1024
printf " contagem dupla %6d MB (paginas compartilhadas)\n", (rss-pss)/1024
printf " so o privado %6d MB (esta e a memoria que morre no OOM)\n", priv/1024
}' "$BRUTO"
if [ "$TEM_SMAPS" -eq 0 ]; then
echo " aviso: kernel sem smaps_rollup, o PSS caiu para RSS (precisa de 4.14+)"
fi
A medição
Rodei o script num contêiner descartável com três processos python3 segurando 96 MB, 48 MB e 16 MB, dentro de um limite de 512 MB. A soma pedida na mão era 160 MB.
Os três processos viraram uma linha só, python3, com 175 MB de PSS e 187 MB de RSS. A soma de RSS de todos os processos deu 194 MB, o PSS deu 177 MB, e a diferença de 16 MB é página compartilhada contada mais de uma vez.
Se os 160 MB fossem medidos só por RSS, o número viria 21% maior do que a reserva realmente consumida.
Como verificar
chmod +x memoria.sh
./memoria.sh
A saída tem quatro partes: as contas do kernel, a tabela de processos ordenada por PSS e a conta que fecha no rodapé. Num kernel 4.14 ou mais novo você vê as colunas PSS e privado preenchidas. Para ver mais linhas:
./memoria.sh 30 | head -8
Confira se a soma do PSS fecha com o consumo que você sabe que existe. Se der disparate para menos, sobrou processo que você não pode ler.
Se der errado
A coluna privado aparece zerada. O Private_Dirty só é ACCOUNTED quando o processo tem o arquivo smaps_rollup legível, o que exige kernel.core_pattern sem restrição e o processo pertencer a você. Em processo de outro usuário, o script cai para VmRSS e a coluna fica em zero.
O total de swap aparece como zero. Máquina com zram mostra SwapTotal menor que a RAM, porque o swap comprimido ocupa menos. Zero só acontece se o kernel foi compilado sem suporte ou se o campo não existe.
O script devolve a lista mas o PSS é igual ao RSS. Faltou /proc/self/smaps_rollup, que só existe a partir do 4.14. O script avisa no fim da saída; nesse caso, compare pela coluna privado, que ainda vem do /proc/[pid]/status.
quando o RSS é a conta certa
Nem sempre PSS ganha. Para decidir se um processo está vazando memória, o que importa é o Private_Dirty, que é memória suja, anônima e exclusiva: 172 MB dos 177 MB do teste eram dessa categoria. RSS alto com PSS baixo é biblioteca compartilhada, sinal de saúde. RSS alto com privado alto e crescendo é vazamento.
Quem trabalha com limite por contêiner tem outra referência: o memory.current do cgroup é o número que o kernel cobra, e inclui cache. O zram muda o que o swap significa.
| Ferramenta | Conta que mostra | Instala | Serve para |
|---|---|---|---|
free |
total, disponível, cache | já está no sistema | ver se a máquina está apertada |
ps aux --sort=-rss |
RSS por processo | já está no sistema | achar o processo grande, knowing que a soma infla |
smem |
PSS por processo | precisa instalar | comparar processos sem contagem dupla |
memoria.sh |
PSS, RSS, privado e a diferença | não instala nada | ver as três contas e a contagem dupla juntas |
Perguntas que aparecem sempre
Por que a soma do RSS passa do total da RAM? Porque página compartilhada é contada inteira em cada processo. Com dez processos Node, a mesma libc entra dez vezes.
PSS está em todo lugar? Não. Só o kernel 4.14 ou mais novo tem /proc/[pid]/smaps_rollup. Antes disso a conta honesta simplesmente não existia.
Cache alto é problema? Não, se for Cached ou SReclaimable, que o kernel devolve sob pressão. Cache que cresce com I/O esperando é disco lento.
Como sei se devo me preocupar? MemAvailable abaixo de 10% do total, por mais de alguns minutos, com Private_Dirty subindo. Uma queda de MemAvailable isolada, sem swap em uso, é o kernel descartando cache, e isso é rotina.
Funciona em contêiner? Funciona, com uma ressalva: o /proc dentro do contêiner mostra os processos do contêiner, então a soma é do grupo, não da máquina. Para a conta da máquina inteira, rode na máquina.
Fontes
O comportamento do /proc/[pid]/smaps_rollup e o significado de Pss, Private_Dirty e Swap estão na documentação do proc(5), conferida em 19 de outubro de 2026, e na documentação do kernel sobre proc, versão 7.3.0. Servidores que guardam estado em arquivo têm o mesmo problema, como mostra a postagem sobre grafo em JSONL.