Skip to main content
RunBook Academy

LinuxXL · Memory PerformanceProcess footprint

RSS, PSS and shared pages - what a process really costs

Advanced⏱ ~19 minps

What you'll learn

  • Explain why the sum of RSS across processes exceeds physical memory
  • Read /proc/PID/smaps_rollup and use Pss to attribute shared pages fairly
  • Distinguish VSZ, RSS, PSS and USS and choose the right one for the question
  • Estimate a working set rather than a footprint, and explain why they differ

Prerequisites

Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-11

Not yet marked complete on this device.

“How much memory does this process use?” has four different correct answers, and choosing the wrong one produces conclusions that are off by a factor of ten. The classic symptom: you sum the RSS column across 40 worker processes, get 90 GB, and the host has 32 GB of RAM and is not swapping.

Nothing is broken. RSS cannot be summed.

Four numbers

ps -o pid,comm,vsz,rss --sort=-rss -p "$(pgrep -f nginx | head -1)"
    PID COMMAND            VSZ   RSS
   1442 nginx           148372 12884
NumberQuestion it answersSums correctly?
VSZ (virtual size)How much address space is mappedNo, and it is mostly meaningless
RSS (resident set)How many pages of mine are in RAMNo - shared pages counted in full by every process
PSS (proportional set)My share of resident pages, dividing shared onesYes - PSS across all processes sums to physical use
USS (unique set)How much would be freed if I killed this processYes, but under-counts the total

VSZ includes everything mapped: file mappings never touched, large reservations, the whole of a memory-mapped 400 GB database file. A process with VSZ of 40 TB is not a problem; a JVM with a large heap reservation shows exactly that. Nobody should page an operator on VSZ.

RSS is real memory - but the double counting is the point:

Physical RAM
  +---------------------------------------+
  | libc.so.6, 2 MB resident, shared      |
  +---------------------------------------+
        ^         ^         ^
        |         |         |
    worker1   worker2   worker3
    RSS +2MB  RSS +2MB  RSS +2MB     -> summed: 6 MB
                                     -> actual: 2 MB
    PSS +0.67 +0.67     +0.67        -> summed: 2 MB  correct

Forty PHP-FPM workers forked from one parent share nearly all their code and much of their heap through copy-on-write. Each one reports the shared pages in full. The sum is arithmetic on a quantity that was never additive.

smaps_rollup

PSS is not in ps. It comes from the kernel’s per-mapping accounting, pre-summed in smaps_rollup:

sudo head -20 /proc/self/smaps_rollup
64ba31bcb000-7fff72171000 ---p 00000000 00:00 0                          [rollup]
Rss:                7920 kB
Pss:                3720 kB
Pss_Dirty:          1372 kB
Pss_Anon:           1372 kB
Pss_File:           2348 kB
Pss_Shmem:             0 kB
Shared_Clean:       5960 kB
Shared_Dirty:          0 kB
Private_Clean:        588 kB
Private_Dirty:      1372 kB
Referenced:         7920 kB
Anonymous:          1372 kB

Reading it:

  • Rss 7920 kB against Pss 3720 kB - more than half of this process’s resident memory is shared with others.
  • Private_Clean + Private_Dirty = 1960 kB is the USS: the memory that would actually be released if this process exited.
  • Pss_Anon versus Pss_File splits the share into heap and stack (anonymous) versus file-backed pages. Anonymous memory can only go to swap; file-backed pages can be dropped and re-read.
  • Shared_Clean 5960 kB is mapped executables and libraries - droppable under pressure at no cost beyond a re-read.

Rank a whole system by PSS:

#!/bin/bash
# Per-process PSS in kB, descending. Requires root for other users' processes.
set -uo pipefail

for p in /proc/[0-9]*; do
  pid=${p#/proc/}
  pss=$(awk '/^Pss:/ {sum += $2} END {print sum+0}' "$p/smaps_rollup" 2>/dev/null) || continue
  [ -z "$pss" ] && continue
  [ "$pss" -eq 0 ] && continue
  printf '%8d %8d %s\n' "$pss" "$pid" "$(tr -d '\0' < "$p/comm" 2>/dev/null)"
done | sort -rn | head -15
  894112     3311 postgres
  412008     1998 java
  188344     2044 dockerd
   94220     1442 nginx
   62118      881 systemd-journal

Those numbers are additive. Their sum plus kernel memory plus page cache accounts for physical RAM, which is the property that makes PSS usable for capacity work.

Footprint is not working set

Every number so far is a footprint: how much memory the process currently holds. The question capacity planning actually needs is the working set: how much it is actively using, and therefore how much you can take away before performance collapses.

They differ enormously. A process can hold 8 GB resident while touching 200 MB per minute; the other 7.8 GB is cold and could be reclaimed at almost no cost. A different process holding the same 8 GB and touching all of it every second cannot lose a byte.

Referenced in smaps_rollup is the accessed-bit total, and it can be used to estimate this. Clear the referenced bits, wait, and read what got touched:

PID=3311
# Clear the referenced bits for this process (root required).
echo 1 | sudo tee "/proc/$PID/clear_refs" > /dev/null
sleep 60
grep -E '^(Rss|Referenced):' "/proc/$PID/smaps_rollup"
Rss:             8388608 kB
Referenced:       204800 kB

8 GB resident, 200 MB touched in a minute. That is a workload whose limit could be cut substantially with little effect - and one where a naive limit set from RSS reserves 40 times more than needed.

Which number for which question

QuestionUse
Is this process leaking?Rss or Pss from smaps_rollup, trended over time
How much memory does this service group cost?Sum of Pss across its processes
How much would I get back by killing this process?Private_Clean + Private_Dirty (USS)
What limit should this service have?memory.peak from its cgroup, plus headroom
Can I safely reduce the limit?Working set from Referenced, plus memory.pressure
Is this process about to be OOM-killed?oom_score and the cgroup’s memory.events

The last row matters because the OOM killer does not use any of these. It scores on RSS plus swap plus page-table size, adjusted by oom_score_adj - so a process with modest PSS but large RSS from shared pages is a more likely victim than its fair share of memory suggests.

Knowledge check

Knowledge check · 5 questions

  1. Q1. Summing the RSS column across 40 forked worker processes gives 90 GB on a 32 GB host that is not swapping. What is wrong?

  2. Q2. A process shows Rss 7920 kB, Pss 3720 kB, Private_Clean 588 kB and Private_Dirty 1372 kB. How much memory is freed by killing it?

  3. Q3. A process holding 8 GB resident while touching only 200 MB per minute can usually have its memory limit reduced substantially without a performance collapse.

  4. Q4. Which are valid reasons to prefer smaps_rollup over smaps? Select all that apply.

  5. Q5. Which quantity does the kernel OOM killer actually score on?

Passing score: 75%. Answers are checked in this browser.