LinuxXL · Memory PerformanceProcess footprint
RSS, PSS and shared pages - what a process really costs
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
“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
| Number | Question it answers | Sums correctly? |
|---|---|---|
| VSZ (virtual size) | How much address space is mapped | No, and it is mostly meaningless |
| RSS (resident set) | How many pages of mine are in RAM | No - shared pages counted in full by every process |
| PSS (proportional set) | My share of resident pages, dividing shared ones | Yes - PSS across all processes sums to physical use |
| USS (unique set) | How much would be freed if I killed this process | Yes, 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:
Rss7920 kB againstPss3720 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_AnonversusPss_Filesplits 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_Clean5960 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
| Question | Use |
|---|---|
| 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
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?
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?
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.
Q4. Which are valid reasons to prefer smaps_rollup over smaps? Select all that apply.
Q5. Which quantity does the kernel OOM killer actually score on?
Passing score: 75%. Answers are checked in this browser.