你看到的内存占用是真的吗

2024/08/14 22:10 pm posted in  Linux

内存记账向来是一笔糊涂账。

故事的起因是这样的,有一小伙发现自己的 Java 应用监控和容器侧监控对不上,他掰着手指给我算年轻代、老年代…,总之就是相较于容器监控,怎么算都有一个不小的缺口,容器的监控有问题。

Linux 的内存统计

对容器环境熟悉的同学肯定已经看出来问题了,问题的根因是「统计口径」,也就是指标差异导致的,但是细数一下,内存占用相关的指标可不少,那么问题来了,在 Linux 环境中,如何计算一个进程的内存消耗呢。

既然要讨论进程,那肯定可以先从 Proc 入手,先看看 Kernel 到底给我们透出来什么信息,一整理,和内存相关的描述有一串:

字段 描述 单位
VmPeak 虚拟内存峰值大小 kB
VmSize 当前虚拟内存大小 kB
VmLck 锁定的内存大小 kB
VmPin 固定的内存大小 kB
VmHWM 物理内存的峰值使用量(高水位标记) kB
VmRSS 当前常驻内存大小(RssAnon, RssFile,RssShmem) kB
RssAnon 匿名页内存大小 kB
RssFile 文件映射内存大小 kB
RssShmem 共享内存大小 kB
VmData 数据段大小 kB
VmStk 栈大小 kB
VmExe 文本段大小 kB
VmLib 共享库的内存 kB
VmPTE 页表的大小 kB
VmPMD 二级页表的大小 kB
VmSwap 交换空间的大小 kB
HugetlbPages 大页的大小 kb

每个参数都是 Linux 进程的内存占用维度,可以看到这维度非常复杂,还涉及到一些和别的进程共享交叉的部分。

要看懂这些参数就不得不说两句 Linux 的虚拟内存管理,一句话概括就是进程看到的内存页地址不是真正的页地址,有个叫做页表的东西进行了映射,使得进程不需要关心底层内存是怎么管理的。

如果每个进程都独占内存页,那么内存记账就变简单且岁月静好,但是会照成大量的内存浪费,因此即使一个进程不主动的使用任何共享内存技术,Linux 仍然会将一些内存页进行共享以节约物理内存,比如使用相同的 lib 库、读相同的文件等。

常见内存指标

既然有这么多内存占用维度,为了简单描述,Linux 自身也有一些指标进行合并同类项:

查询指标 说明 计算公式
RES RSS映射的物理内存。 anno_rss + file_rss + shmem_rss
SHR 共享内存。 file_rss + shmem_rss
MEM% 内存使用率。 RES / MemTotal
RSS RSS映射的物理内存。 anno_rss + file_rss + shmem_rss
MEM% 内存使用率。 RSS / MemTotal
USS 独占内存。 anno_rss
PSS 按比例分配内存。 anno_rss + file_rss/m + shmem_rss/n
RSS RSS映射的物理内存。 anno_rss + file_rss + shmem_rss

监控中最常见的就是 RSS 了,表示进程在物理内存中占用的空间大小,包括共享库占用的内存以及进程自身占用的内存。RSS 涵盖全、易获取,是居家必备指标项。但是 RSS 也有一些不足,这也是 WorkingSet 出现的原因。

WorkingSet

WSS(Memoy Working Set Size)指标是一种更为合理评估进程内存真实使用内存的计算方式。但目前 WSS 还只是概念,并没有哪一个工具可以正确统计出WSS,只能是趋近。

容器中的 WorkingSet 的指标来自 cAdvisor。cAdvisor(Container Advisor)是一个 Google 开源的容器资源使用和性能分析工具,主要用于监控容器的资源消耗情况。流水的容器运行时,铁打的 cAdvisor,在胖瘦容器之争时,cAdvisor 就已经是容器监控的标配了。

RSS 局限在并不能体现 cache 的内存占用,尤其是对 pagecache 重依赖的应用(比如 ES),WorkingSet 的设计是为了提供一个相对「实际」的内存占用,包括了和该进程相关的所有不能轻易释放的内存(相关逻辑见文末链接)。

Memory Usage = Memory WorkingSet = usage_in_bytes - total_inactive_file = rss + active cache

当通过 kubectl top po 时,展示的便是 WorkingSet 内存占用,并且 K8s 中 memory limit 也是以 WorkingSet 作为依据。

后续

那开头的小伙遇到的是什么问题呢,原因是他的应用存在文件操作,所以存在数量不小的 active cache,而他的应用监控并没有统计这一部分内存的占用。

在进行友好沟通之后,结论就是容器内存看板从此改名为 WorkingSet 内存 ε=(´ο`*)))。

参考: