Linux 进程内存监控:Linux 内存调优之进程内存深度监控
我看远山,远山悲悯
写在前面
- 博文内容涉及 Linux 进程内存监控
- 监控方式包括传统工具
ps/top/pmap
,以及cgroup
内存子系统,proc
内存伪文件系统 - 监控内容包括
进程内存使用情况
,内存全局数据统计
,内存事件指标
,以及进程内存段数据监控
- 理解不足小伙伴帮忙指正 :),生活加油
我看远山,远山悲悯
持续分享技术干货,感兴趣小伙伴可以关注下 ^_^
监控进程的内存使用量
这里分析的工具主要是原生工具,后面还会分享一些 BPF 相关的内存观察工具以及系统内存的全局监控
PS/TOP
一般的内存监控工具,对于进程级别的,会使用如 ps/top
命令, 通过指标 VIRT
或 VSZ
和 RES
或 RSS
来区分两种不同的统计数据
VIRT
或VSZ
代表进程申请的虚拟内存
大小,RES
或RSS
代表的是虚拟内存当前实际映射的物理内存
大小,也叫常驻内存。
1 | ┌──[root@vms100.liruilongs.github.io]-[~] |
所以如果通过上面的命令,查看应用实际使用的内存大小,需要查看 RES(RSS(KB单位))
列,表示进程当前驻留在物理内存中的内存总量(即没有被交换到磁盘的部分)。
RSS
包含的内容:
- 进程独有的数据(如堆、栈、私有匿名页)。
+ 共享内存页(如共享库、共享内存 IPC 等)。
1 | ┌──[root@liruilongs.github.io]-[~] |
共享内存
需要说明的是,进程是共享物理内存页帧的
。比如使用相同库函数的两个进程
,就可以共享使用相同的物理内存页来存储库文件代码。它们各自的 RSS(Resident Set Size)
值会将该共享页的物理内存(SHR 列)
重复计入每个进程的 RSS(RES)
。因此 进程的 RSS 总和 可能会明显超过 系统实际的物理内存容量
。真实物理内存占用 = 独占内存(RES) - 共享内存SHR(Shared Memory)
。
SHR
列 :进程占用的 共享物理内存(如共享库、共享内存 IPC)。
Cgroup 子系统
通过 Cgroup
子系统来获取内存信息,在获取之前需要获取当前进程的PID
以及对应的 Cgroup
分组
获取 htop
进程ID, htop 是一个类似 top 的系统整体性能监控的进程
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
通过 ps 命令获取 htop 对应的 Cgroup 分组
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
我们只关注内存子系统的,所以直接看内存的分组: 8:memory:/user.slice/user-1000.slice/session-1.scope
,这里的 8 表示 Cgroup 层级
可以通过下面的命令查看 Cgroup
层级以及 当前系统挂载了多少子系统
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
user.slice/user-1000.slice/session-1.scope
表示 Cgroup 层级树
user.slice
:表示该 cgroup 属于 用户会话层级,与用户进程相关(与 system.slice 系统服务层级区分)user-1000.slice
:表示用户 ID 为 1000 的普通用户(Linux 中 UID 1000 通常是首个创建的非 root 用户)session-1.scope
:表示该用户的 会话单元(如一个终端会话或登录会话),属于临时性资源组(scope 用于管理短生命周期的进程组)
htop
是一个前台进程,通过 Cgroup
资源树可以很清晰的看到,下面来看下 Cgroup
内存子系统观察进程内存信息的一些指标文件
内存详细信息指标监控
下面一组是内存详细信息的数据统计
参数 | 作用 |
---|---|
memory.numa_stat |
NUMA 节点的内存使用统计(适用于多 CPU 架构)。 |
memory.stat |
详细内存使用统计 |
在这之前我们先介绍一个特殊的值,memory.limit_in_bytes 这个可能是我们接触最多的参数,用于进程 物理内存资源限制
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
可以看到上面的 htop, 文件中的值 9223372036854771712
表示当前 cgroup
的内存限制处于无限制状态,当 cgroup
未显式设置内存限制时,内核会默认将此值设为 PAGE_COUNTER_MAX
,该值由内核通过 LONG_MAX / PAGE_SIZE * PAGE_SIZE
计算得出,确保与内存页对齐.
memory.stat
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
以下是关键参数的说明
memory.stat
核心参数解析表
参数分类 | 参数名 | 值(字节) | 说明 | 相关引用 |
---|---|---|---|---|
基础内存使用 | cache |
393,973,760 | 文件缓存和 tmpfs/shmem 内存,用于加速文件访问(可回收) | |
rss |
301,170,688 | 进程匿名内存(堆、栈等)占用,反映实际物理内存使用量 | ||
swap |
0 | 当前 cgroup 使用的交换空间大小,非零表示物理内存不足 | ||
内存页管理 | active_anon |
147,456 | 活跃的匿名内存页(正在使用的堆、栈内存) | |
inactive_anon |
302,727,168 | 非活跃的匿名内存页(可被回收的堆、栈内存) | ||
active_file |
44,892,160 | 活跃的文件缓存页(近期被频繁访问的文件数据) | ||
inactive_file |
348,233,728 | 非活跃的文件缓存页(长时间未访问的文件数据,优先回收) | ||
内存事件 | pgpgin |
241,505 | 从磁盘换入内存的页数,高值可能反映频繁 I/O | |
pgpgout |
113,977 | 从内存换出到磁盘的页数 | ||
pgmajfault |
260 | 需磁盘 I/O 的硬缺页次数,高值可能引发性能问题 | ||
层级管理 | hierarchical_memory_limit |
9,223,372,036… | 层级化内存限制(当前值为极大数,表示未启用限制) | |
total_* 系列(如 total_rss ) |
与同名参数一致 | 包含当前 cgroup 及其子 cgroup 的总统计值(如 total_rss 表示层级内所有进程的匿名内存总和) |
||
其他 | rss_huge |
171,966,464 | 透明大页(THP)占用量,大页可减少内存管理开销 | |
unevictable |
0 | 不可回收的内存(如 mlock 锁定的内存) |
- 单位与换算:上述值均为 字节(Bytes),可通过
1 GB = 1073741824 Bytes
转换为更易读的单位。例如:
•cache = 393,973,760 Bytes ≈ 376 MB
•rss = 301,170,688 Bytes ≈ 287 MB
- 关键场景判断:
• 内存压力:若rss
接近hierarchical_memory_limit
,需警惕 OOM(内存耗尽)风险。
• 缓存优化:若inactive_file
较高,可通过sync; echo 3 > /proc/sys/vm/drop_caches
手动回收缓存。 - 与
free
/vmstat
的关系:
•free -m
中的buff/cache
对应cache + buff
(部分系统可能合并统计)。
•vmstat
的si
/so
对应pgpgin
/pgpgout
的实时动态变化。
如需进一步分析具体进程的内存行为,可结合 /proc/<PID>/smaps
或工具如 smem
。
memory.numa_stat
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
以下是对 memory.numa_stat
输出参数的详细解释
参数名 | 类型 | 描述 | |
---|---|---|---|
total | 基础统计项 | 当前 cgroup 在 NUMA 节点上的总内存占用(单位字节),等于 anon + file + unevictable 之和 |
|
file | 基础统计项 | 文件页缓存(File-backed memory)占用量,例如程序文件、共享库等通过文件映射的内存 | |
anon | 基础统计项 | 匿名页(Anonymous pages)和 Swap 缓存的总量,例如堆、栈等动态分配的内存 | |
unevictable | 基础统计项 | 不可回收的内存页(例如被锁定或标记为不可回收的页面) | |
hierarchical_total | 层级统计项 | 包含所有子 cgroup 的总内存占用(单位字节),统计范围覆盖当前 cgroup 及其子级 | |
hierarchical_file | 层级统计项 | 包含所有子 cgroup 的文件页缓存总量 | |
hierarchical_anon | 层级统计项 | 包含所有子 cgroup 的匿名页和 Swap 缓存总量 | |
hierarchical_unevictable | 层级统计项 | 包含所有子 cgroup 的不可回收内存总量 |
补充说明:
- NUMA 节点标识:输出中的
N0=169929
表示当前统计值属于 NUMA 节点 0(N0
)。在 NUMA 架构中,每个节点的本地内存访问速度更快,跨节点访问会增加延迟。 - 层级统计的意义: 带
hierarchical_
前缀的参数表示当前 cgroup 及其所有子 cgroup 的累积内存使用量。例如,hierarchical_total
是当前 cgroup 和子 cgroup 在所有 NUMA 节点上的内存总量。 - 应用场景:通过对比
total
和hierarchical_total
,可判断子 cgroup 的内存分配是否合理。若unevictable
值较高,可能需排查是否有进程误用内存锁定(如mlock
)。
上面的输出信息中提供的输出中,total=169929 N0=169929
表示该 cgroup 仅在 NUMA 节点 0 上分配了 169,929 字节内存。其中:
• 文件页缓存占 95,978 字节(file=95978
),
• 匿名页和 Swap 缓存占 73,951 字节(anon=73951
),
• 无不可回收内存(unevictable=0
)。
内存事件指标监控
下面为内存事件指标依次来看一下
参数 | 作用 |
---|---|
memory.usage_in_bytes |
当前物理内存使用量(包括匿名内存、文件缓存等)。 |
memory.memsw.usage_in_bytes |
当前物理内存 + swap 总使用量。 |
memory.failcnt |
记录内存限制触发的失败次数(超出 memory.limit_in_bytes 的次数)。 |
memory.events |
内存事件计数器(如 oom 溢出次数、under_oom 低内存状态)。 |
memory.events.local |
同上,但仅统计当前 cgroup(不包含子 cgroup)。 |
这里我们使用 tuned
这个服务,这是一个系统调优的服务,作为一个独立的 service unit
存在,所以会有一个单独 Cgroup
分组
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
可以在 Cgroup
路径下面看到所有的指标数据 /sys/fs/cgroup/memory/system.slice/tuned.service/
,还有部分内核相关的,这里我们只看一下内存相关的
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
memory.usage_in_bytes : 当前 cgroup 中所有进程实际使用的物理内存总量(包括 RSS 和 Page Cache),单位字节(约 15.86 MB)。
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
memory.memsw.usage_in_bytes : 当前 cgroup 中所有进程使用的物理内存 + Swap 空间的总量(单位字节)。此处与物理内存相等,说明未使用 Swap。
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
memory.failcnt 存使用达到 memory.limit_in_bytes 设定的限制值的次数。值为 0 表示未触发过内存超限。
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
memory.events 内存事件计数器:
- low: 低内存压力事件次数
- high: 高内存压力事件次数
- limit_in_bytes: 达到内存限制的次数
- oom: OOM(内存耗尽)触发次数。全为 0 表示无相关事件发生。
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
当然还有一些其他的参数,感兴小伙伴可以研究下,通过去读上面的参数可以进行性能分析,系统监控
proc 内存伪文件系统
如果需要详细查看一个进程使用了哪些虚拟地址
,可用使用 pmap PID
命令或者基于 proc
内存伪文件系统查看内存详细信息,比如 /proc/1/status
,/proc/PID/maps
和/proc/PID/smaps
等,
查看进程详细内存段数据
pmap 1002
快速查看进程的虚拟内存布局和总占用。
1 | ┌──[root@liruilongs.github.io]-[~] |
对应的列分别表示:内存段的起始虚拟地址(如 0000561ae3586000)
。内存段的大小(如 4K)。内存访问权限 (r)可读 |(w)可写 |(x)可执行 |(s)共享 |(p)私有
/proc/1/maps
详细列出所有内存段的地址范围、权限和映射文件
1 | ┌──[root@liruilongs.github.io]-[~] |
可以看到在上面的基础上,展示了内存段的起始和结束虚拟地址(如 55e05a46b000-55e05a471000)
。多了映射文件在文件中的偏移量(十六进制,如 00000000)
。文件所在设备的编号(格式 fd:00,主设备号:次设备号)
。文件的 inode 编号(如 372298)
。
/proc/1/smaps
提供每个内存段的详细物理内存统计(RSS、PSS、共享/私有内存等)
1 | ┌──[root@liruilongs.github.io]-[~] |
部分字段说明:
- Size: 内存段的虚拟大小(如 24 kB)。
- Rss:(Resident Set Size) 实际占用的物理内存(包含共享内存)。
- Pss:(Proportional Set Size) 按共享比例计算的物理内存(如 3 个进程共享 24KB → 每个进程 Pss 8KB)。
- Shared_Clean/Shared_Dirty: 共享内存中未修改/已修改的部分。
- Private_Clean/Private_Dirty: 私有内存中未修改/已修改的部分。
- Swap: 被交换到磁盘的内存大小。
- VmFlags: 内存段的属性标志(如 rd 可读,mr 可映射,mw 可写等)。
进程全局内存数据统计
status 用于展示当前进程的一些基本指标,进程基础信息,权限与身份,信号与中断等
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
这里只关注内存相关的
虚拟内存核心指标
参数 | 值 | 描述 | |
---|---|---|---|
VmPeak | 165,112 kB | 进程生命周期内虚拟内存的 峰值(含已分配但未使用的内存),反映进程曾达到的最大内存需求 | |
VmSize | 100,636 kB | 当前进程 虚拟地址空间总大小(包括代码、数据、堆、栈等所有映射区域),约 98.27 MB |
物理内存核心指标
参数 | 值 | 描述 | |
---|---|---|---|
VmHWM | 12,688 kB | 进程物理内存使用 峰值(Resident Set Size 最大值),约 12.38 MB | |
VmRSS | 12,688 kB | 当前实际驻留物理内存(RSS),等于 RssAnon + RssFile + RssShmem ,约 12.38 MB |
|
RssAnon | 4,084 kB | 匿名页(动态分配的堆/栈内存)占用的物理内存,例如 malloc 分配的未映射文件的内存 |
|
RssFile | 8,604 kB | 文件页缓存占用的物理内存(如加载的共享库、内存映射文件) | |
RssShmem | 0 kB | 共享内存段(如 shmget 创建的 IPC 内存)占用的物理内存 |
内存区域细分指标
参数 | 值 | 描述 | |
---|---|---|---|
VmData | 19,052 kB | 数据段 + 堆 的虚拟内存大小(动态分配的内存通过 brk 或 mmap 扩展) |
|
VmStk | 1,036 kB | 栈空间 的虚拟内存大小(存放局部变量和函数调用帧),默认上限由 ulimit -s 控制 |
|
VmExe | 884 kB | 可执行代码段 的虚拟内存大小(程序本身的机器指令,只读) | |
VmLib | 8,832 kB | 共享库 的虚拟内存大小(如 glibc 等动态链接库) |
其他关键参数
参数 | 值 | 描述 | |
---|---|---|---|
VmLck | 0 kB | 锁定的物理内存(通过 mlock 系统调用防止被换出到 Swap),常用于实时性要求高的场景 |
|
VmPTE | 84 kB | 页表项 占用的物理内存(用于管理虚拟地址到物理地址的映射关系) | |
VmSwap | 0 kB | 当前已换出到 Swap 分区 的内存大小(若值持续增长,需排查内存泄漏或物理内存不足) |
关键应用场景分析
内存泄漏检测
- 对比
VmSize
和VmRSS
:若VmSize
持续增长而VmRSS
稳定,可能为虚拟内存分配过多但未实际使用(如未初始化的malloc
)。 - 监控
VmSwap
:若长期非零,需检查物理内存是否不足或进程存在内存滥用。
性能优化方向
- 共享库优化:
VmLib
值较高时,可考虑静态链接或减少动态库依赖以降低内存开销。 - 堆栈管理:
VmData
和VmStk
异常增长可能提示堆内存泄漏或递归调用过深。
系统资源分配
VmHWM
可用于设置 CGroup 内存限制(memory.max_usage_in_bytes
),避免单个进程耗尽物理内存。RssFile
较高时,可通过清理缓存(sync; echo 1 > /proc/sys/vm/drop_caches
)释放非关键文件缓存。
statm 用于展示进程内存快照,但需注意 单位是内存页,通常 1 页 = 4 KB
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
字段解析
参数 | 值 | 描述 | 换算为 KB | |
---|---|---|---|---|
size | 25159 | 进程虚拟地址空间总大小(含代码、数据、堆栈等所有映射区域) | 25159 × 4 ≈ 100,636 KB | |
Resident | 3172 | 实际驻留物理内存(RSS),即当前进程使用的物理内存总量 | 3172 × 4 ≈ 12,688 KB | |
Shared | 2151 | 共享内存页数(如动态链接库、共享内存段等被多个进程共享的部分) | 2151 × 4 ≈ 8,604 KB | |
Trs | 221 | 可执行代码段(Text Resident Set)占用的内存页(如程序自身的机器指令) | 221 × 4 ≈ 884 KB | |
Lrs | 0 | 库的内存页数(Linux 2.6+ 中已废弃,通常为 0) | - | |
Drs | 5022 | 数据段(堆、全局变量)和用户态栈的总内存页 | 5022 × 4 ≈ 20,088 KB | |
dt | 0 | 脏页数量(已修改但未写入磁盘的页,Linux 2.6+ 中已废弃) | - |
与 /proc/1/status
的关联 对比 /proc/1/status
中的内存参数可验证数据一致性
• VmSize: 100636 kB
= size × 4
= 25159 × 4
• VmRSS: 12688 kB
= Resident × 4
= 3172 × 4
• RssFile: 8604 kB
≈ Shared × 4
= 2151 × 4
• VmExe: 884 kB
= Trs × 4
= 221 × 4
• VmData + VmStk: 19052 + 1036 = 20088 kB
≈ Drs × 4
= 5022 × 4
smaps_rollup 提供进程 全局汇总统计(如总 RSS、PSS、Swap 等), 读取 smaps_rollup
比遍历 smaps
更高效(减少锁竞争时间),适合高频监控场景,
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
核心内存统计
参数 | 值 | 描述 | |
---|---|---|---|
Rss | 12,692 kB | 常驻物理内存总量(包含共享和私有内存),等于 Shared_Clean + Shared_Dirty + Private_Clean + Private_Dirty |
|
Pss | 4,648 kB | 比例集内存(按共享比例分摊后的内存),Pss = Pss_Anon + Pss_File + Pss_Shmem |
|
Pss_Anon | 3,463 kB | 匿名页(如堆、栈)分摊后的内存,反映独占或部分共享的匿名内存 | |
Pss_File | 1,184 kB | 文件页缓存(如共享库、映射文件)分摊后的内存 | |
Pss_Shmem | 0 kB | 共享内存(如 tmpfs )的分摊内存,此处未使用 |
共享与私有内存分布
参数 | 值 | 描述 | |
---|---|---|---|
Shared_Clean | 8,540 kB | 共享的未修改内存(如只读共享库),可被内核直接回收 | |
Shared_Dirty | 936 kB | 共享的已修改内存(如被多个进程写入的共享内存),需同步到磁盘后才能回收 | |
Private_Clean | 64 kB | 私有的未修改内存(如未修改的私有数据),可快速回收 | |
Private_Dirty | 3,152 kB | 私有的已修改内存(如进程堆内存),需写入 Swap 或文件后才能回收 |
其他关键参数
参数 | 值 | 描述 | |
---|---|---|---|
Anonymous | 4,088 kB | 匿名内存总量(无法关联文件的内存,如 malloc 分配的内存) |
|
Swap | 0 kB | 已换出到 Swap 分区的内存量,此处为 0 表示未启用 Swap 或内存充足 | |
Locked | 0 kB | 通过 mlock 锁定的内存(不可被换出),常用于实时性要求高的场景 |
|
Referenced | 12,692 kB | 最近被访问过的内存页,反映当前活跃内存 |
这是两个 OOM 内存杀手相关的,这里不多讲,在之后的博客中会和小伙伴分享
1 | ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] |
博文部分内容参考
© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)
《 Red Hat Performance Tuning 442 》
《性能之巅 系统、企业与云可观测性(第2版)》
© 2018-至今 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)
Linux 进程内存监控:Linux 内存调优之进程内存深度监控
https://liruilongs.github.io/2025/04/11/待发布/Linux-进程内存监控:Linux-内存调优之进程内存深度监控/