RSS、VSZ、PSS 各自量的是什么
一句话总结
VSZ 是承诺的地址空间,RSS 是实际驻留的物理内存,PSS 是按使用者分摊后的共享内存份额。把多个 Pod 的 RSS 相加会高估实际用量——此时应查看 PSS。
为什么需要这些知识
“看起来像内存泄漏”的报障,有一半并非真正泄漏。进程变大的原因有很多。
- 真正的泄漏:未释放的内存分配不断累积
- 缓存增长:应用程序有意填充缓存,到达上限后会停止
- 堆碎片:内存已经释放,却无法归还给操作系统
- 分配器特性:glibc malloc 不太会把小块内存归还给操作系统
四种情况的处理方式不同。如果不加区分,只选择“增加内存”,几天后同样的问题还会重现。
它是如何运作的
查看单个进程的真实使用量,smaps_rollup 最准确。
grep -E '^(Rss|Pss|Private_Dirty|Swap):' /proc/<PID>/smaps_rollup
| 项目 | 含义 |
|---|---|
Rss |
驻留在物理内存中的全部内容,包括共享库 |
Pss |
共享部分按使用者数量分摊后的份额——计算总量时唯一应使用的值 |
Private_Dirty |
仅由该进程使用、无法回写文件的部分——判断泄漏时看这里 |
判断泄漏要看趋势,而不是某一时点的值。保持负载恒定,每隔几分钟记录一次 Private_Dirty;如果持续单调增长,更可能是泄漏;如果达到某条线后保持平坦,更可能是缓存。
常见误解
看到 VSZ 就惊慌。 64 位 JVM 或 Go runtime 可能保留数十 GB 的 VSZ,但这只是预留地址空间,并非物理内存。VSZ 几乎总可以忽略。
把 RSS 相加。 10 个容器使用同一个 libc 时,相关页面在物理内存中只有一份,却会在 RSS 中计算 10 次。求总量时应使用 PSS。
看到 free -h 中的 free 很小就担心。 Linux 会把所有闲置内存用于 page cache,真正应该查看的是 available。
从哪里查看容器内存限制
宿主机的 free 或 top 不知道容器获得了多少份额,应直接查看 cgroup。
cat /sys/fs/cgroup/memory.max # 한도 (max 면 제한 없음)
cat /sys/fs/cgroup/memory.current # 지금 쓰는 양
cat /sys/fs/cgroup/memory.stat # 항목별 내역
memory.current 包含 page cache。 大量读取文件的进程会因为缓存看起来接近上限,但出现内存压力时,内核会先丢弃缓存,所以不一定发生 OOM。因此,“内存使用率达到 90%”并不等于危险。
是否真的危险,可以通过 memory.stat 中的两行判断。
anon 1234567890 ← 익명 메모리. 이건 버릴 수 없다
file 987654321 ← 페이지 캐시. 압박이 오면 버려진다
如果 anon 接近上限,就即将发生 OOM。
如何确认是否发生 OOM
Pod 突然重启,而日志中什么也没有,通常就是 OOM。应用程序无法知道自己即将被终止,因此来不及留下最后一条消息。
kubectl describe pod <파드> | grep -A3 "Last State"
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
# cgroup 쪽 기록
cat /sys/fs/cgroup/memory.events
oom 3
oom_kill 1
oom 表示触及上限并尝试回收的次数,oom_kill 表示实际终止进程的次数。如果只有 oom 增长,而 oom_kill 仍为 0,说明虽然尚未被终止,却一直承受内存压力,延迟从此时就会开始恶化。忽略这个信号,现象看起来只会是“偶尔变慢”。
各语言应调节什么
即使缩小容器限制,如果 runtime 不知道这个限制,也没有意义。
| runtime | 应调节的项目 | 不调节的后果 |
|---|---|---|
| JVM | -XX:MaxRAMPercentage=75 |
旧版 JVM 会依据宿主机内存确定堆大小 |
| Node.js | --max-old-space-size=<MB> |
默认堆可能大于容器限制 |
| Go | GOMEMLIMIT |
GC 运行太晚,导致超过限制 |
| Python | 无单独设置 | 通过 worker 数量(gunicorn -w)调节 |
GOMEMLIMIT 从 Go 1.19 开始提供。设为限制的大约 90%,GC 就会在接近该线时更频繁运行。如果没有它,把 Go 服务放入内存限制很小的容器时,GC 会悠闲运行,最终遭遇 OOM。
生产环境中真正重要的事
容器不是按宿主机内存,而是按 cgroup 限制被终止。
cat /sys/fs/cgroup/memory.max # 한도
cat /sys/fs/cgroup/memory.current # 지금
cat /sys/fs/cgroup/memory.events # oom_kill 횟수
memory.current 也包含 page cache。因此,大量读取文件的进程即使实际堆很小,也可能触及限制。必须区分 memory.stat 中的 anon(匿名内存)和 file(缓存),才能看出真正原因。OOM kill 由内核执行,不会在应用程序日志中留下任何内容——很多时候,memory.events 中的 oom_kill 是否增加是唯一证据。