LabHub
学习 学习路径 课程

CPU 与内存泄漏的判定

RSS、VSZ、PSS 各自量的是什么

在 LabHub 中继续学习

一句话总结

VSZ 是承诺的地址空间,RSS 是实际驻留的物理内存,PSS 是按使用者分摊后的共享内存份额。把多个 Pod 的 RSS 相加会高估实际用量——此时应查看 PSS。

概念图: 把多个 Pod 的 RSS 相加会高估实际用量 · 计算总量时唯一应使用的值 · 判断泄漏时看这里 · 趋势,而不是某一时点的值

为什么需要这些知识

“看起来像内存泄漏”的报障,有一半并非真正泄漏。进程变大的原因有很多。

四种情况的处理方式不同。如果不加区分,只选择“增加内存”,几天后同样的问题还会重现。

它是如何运作的

查看单个进程的真实使用量,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

从哪里查看容器内存限制

宿主机的 freetop 不知道容器获得了多少份额,应直接查看 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 是否增加是唯一证据。