“Process này tốn bao nhiêu RAM?” — câu hỏi tưởng đơn giản. Trên Linux, tùy bạn nhìn VSS/VSZ, RSS, hay USS, số có thể chênh vài lần, và cộng RSS của mọi process thường lớn hơn RAM máy (vì shared bị đếm chồng).
Bài này là mental model khi debug container OOM, pod
memory, hay so sánh worker Go / JVM / Python — không
phải thay man page đầy đủ.
Demo đếm trên 2 process giả (shared lib + SHM): VSS / RSS / USS / PSS + SHM.
Ba (thật ra bốn) con số
-
VSS / VSZ — Virtual Set Size: tổng address space
ảo đã map (kể cả chưa fault vào RAM, file map, reservation…).
psVSZ,topVIRT. -
RSS — Resident Set Size: page đang nằm trong RAM,
kể cả page shared (đếm full cho mỗi process).
psRSS,topRES. -
USS — Unique Set Size: page chỉ process này map;
kill process thì “trả” phần này.
smem/smaps. -
PSS — Proportional Set Size: unique + (shared ÷
số process đang share). Cộng PSS mọi process ≈ phân bổ “công
bằng” hơn.
smem,smaps_rollup.
Quan hệ thô (không phải định lý tuyệt đối trên mọi kernel config):
VSS ≥ RSS ≥ PSS ≥ USS ≥ 0
// RSS có thể > “fair share” vì shared đếm full
// Σ RSS (mọi process) thường > RAM dùng thật
// Σ PSS ≈ hữu ích hơn khi ước lượng “ai đang giữ bao nhiêu”
VSS — ảo, dễ “to đùng”
Mỗi process có không gian địa chỉ ảo. Map thư viện, map file,
mmap lớn, heap, stack, vùng reserve… đều làm
VSS tăng, kể cả khi phần lớn page
chưa fault vào RAM (demand paging).
- JVM / large heap config, Tensor buffer map, elastic mapping → VSS hàng GB trong khi RSS chỉ vài trăm MB là chuyện thường.
- Đừng dùng VSS để kết luận “hết RAM”. Nó trả lời: “address space rộng cỡ nào?”, không phải “đang ngồi bao nhiêu DRAM”.
ps -o pid,vsz,rss,comm -p $PID
# VSZ, RSS đơn vị KB (ps cổ điển)
RSS — đang resident, nhưng shared bị đếm chồng
RSS = các page có frame trong RAM thuộc mapping của process. Gồm:
- Anonymous private (heap, stack, brk…) — thường “của riêng”
-
File-backed đã fault (code
libc.so, filemmap…) — nhiều process dùng chung page vật lý -
Shared memory (POSIX/System V SHM,
mmap MAP_SHARED) — cùng một vùng RAM, mỗi process map vào RSS của mình
Hệ quả vận hành:
2 worker, mỗi RSS = 200MB
trong đó ~80MB là libc + lib chung (cùng page)
+ 50MB SHM dùng chung cả hai
Cộng 200+200 = 400MB ← ảo giác
Thực trên máy có thể ~200 - 80 - 50 + … ≈ thấp hơn nhiều
OOM killer / cgroup memory vẫn có quy tắc riêng (RSS, cache, shmem
accounting…). Điểm cần nhớ: cộng cột RES trên
top không ra “RAM còn lại”.
USS — “nếu kill process này, được lại bao nhiêu?”
USS chỉ đếm page private / unique: không share với ai. Kill process → phần đó free (trừ khi còn ref khác ngoài model đơn giản này).
- So sánh “worker này phình heap không” → USS (hoặc private trong smaps) sát hơn RSS.
- 10 process fork + copy-on-write: lúc đầu RSS mỗi đứa có thể cao (share page), USS thấp; sau khi ghi đè page, USS tăng (COW break).
# smem (python) — cột USS PSS RSS
smem -p $PID
smem -r # sort RSS
# hoặc tự cộng từ smaps:
# Private_Clean + Private_Dirty ≈ hướng USS
PSS — chia shared “công bằng”
Page private: tính full. Page đang được N process map: mỗi process
chịu size/N.
PSS ≈ USS + Σ (shared_page_size / n_sharers)
Muốn trả lời “trên máy này, service X ‘đáng’ bao nhiêu RAM nếu phân bổ chi phí lib chung” → PSS. Muốn “kill X được bao nhiêu” → USS. Muốn “kernel đang giữ resident cho mapping của X” → RSS.
SHM — shared memory, chỗ RSS hay đánh lừa
SHM (System V shmget/shmat,
POSIX /dev/shm, mmap(..., MAP_SHARED),
đôi khi cả tmpfs) là vùng RAM (hoặc tmpfs) nhiều process cùng map.
- Mỗi process attach: vùng đó vào RSS (và VSS) của process đó.
- USS của mỗi process không ôm trọn SHM (vì không unique) — trừ khi chỉ còn một process map.
- PSS: mỗi bên chịu 1/N kích thước segment.
-
Trên hệ thống: shmem hay hiện trong
free/MemAvailableaccounting khác anonymous heap — IPC lớn (Redis không, nhưng app tự SHM, DB segment, ML shared weight map…) cần nhìn riêng.
ipcs -m # System V shm segments
ls -lh /dev/shm # POSIX shm objects (tmpfs)
# trong smaps: mapping có path /dev/shm/... hoặc SYSV...
# cgroup v2: memory.stat → shmem, file, anon…
Docker/K8s: limit memory container tính theo cgroup (thường gồm
anon + file cache + shmem theo rule version). Process RSS cao vì
map SHM lớn có thể đẩy cgroup chạm limit dù “logic app” tưởng chỉ
dùng ít heap — đọc memory.stat / metrics runtime, đừng
chỉ nhìn một số ps.
Nhìn số trên máy
# nhanh
ps -o pid,vsz,rss,comm -p $PID
cat /proc/$PID/status | egrep 'VmSize|VmRSS|VmData|RssAnon|RssFile|RssShmem'
# chi tiết từng mapping
less /proc/$PID/smaps
cat /proc/$PID/smaps_rollup # kernel mới: Pss, Rss, … gộp
# tổng quan “fair”
smem -t # tổng USS/PSS/RSS
Gợi ý đọc status:
VmSize≈ hướng VSSVmRSS≈ RSS-
RssAnon/RssFile/RssShmem— tách anonymous vs file vs shmem (hữu ích hơn một số RSS)
Khi nào dùng metric nào
-
OOM / “máy hết RAM?” — nhìn host
MemAvailable, cgroupmemory.current+memory.stat, không cộng VSZ. -
Process leak heap? — USS hoặc
RssAnontheo thời gian (RSS có thể nhiễu vì cache file). - N replica cùng image? — đừng × N × RSS; lib shared + image layer page cache được share (PSS / cgroup chung node).
-
SHM / mmap shared lớn? — kiểm tra
RssShmem,/dev/shm,ipcs; nhớ RSS per-process đếm chồng. - Capacity planning “mỗi pod thêm bao nhiêu?” — gần USS (private) + phần shmem/cache riêng policy; PSS nếu muốn amortize lib.
Liên hệ app (Go / JVM / Python / Nest…)
- Runtime allocator (Go GC, JVM heap, Python arena) chủ yếu đẩy anonymous / USS khi sống lâu + fragment.
-
Native lib, model weights
mmap, extension module → file-backed RSS share được giữa process. - Worker prefork (Gunicorn, cũ PHP-FPM…): COW — fork xong RSS trông “đắt”, USS mới lộ sau khi request ghi page.
Đọc thêm
Heap managed (JVM/Go/V8/Python) còn lớp garbage collector — RSS process ≠ “heap live set”.
Một câu để nhớ
VSS = bản đồ ảo. RSS = đang ở RAM (shared đếm full mỗi process). USS = phần riêng, kill là mất. PSS = chia shared cho công bằng. SHM nằm ở shared — làm RSS cộng dồn sai nếu không để ý.
Debug memory: chọn đúng câu hỏi trước, rồi mới chọn cột số. Cộng nhầm RSS/VSS là cách nhanh nhất để “tối ưu” một vấn đề không tồn tại — hoặc bỏ lỡ leak anonymous thật.