>_ josetilabs.com
← blog

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.

bash
#!/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.

Terminal mostrando, num container com 160 MB de carga em tres processos, que o RSS somado marca 194 MB e o PSS marca 177 MB, com 16 MB de contagem dupla
Antes e depois de subir a carga: o mesmo comando, a mesma máquina, 160 MB a mais nos processos e 16 MB de contagem dupla aparecendo do nada.

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

bash
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:

bash
./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.

Leia também

// Por e-mail

Os textos publicados aqui também vão por e-mail. O envio ainda não começou: quem deixar o endereço agora entra na primeira lista.

Guardo só o seu e-mail, a data e o registro desta autorização. Sem cookies, sem rastreador e nada compartilhado com terceiros. Detalhes na política de privacidade.