「Linux」- 进程内存占用分析

  CREATED BY JENKINSBOT

问题描述

问题现象很简单,系统内存使用情况很高,如下命令输出所示:

# uptime 
 23:42:48 up 34 days, 13:21, 12 users,  load average: 0.52, 0.75, 0.85
 
# free -h
              total        used        free      shared  buff/cache   available
Mem:           15Gi        12Gi       541Mi       1.1Gi       2.6Gi       1.7Gi
Swap:          29Gi        11Gi        18Gi

# top
top - 23:45:16 up 34 days, 13:24, 12 users,  load average: 0.59, 0.68, 0.81
Tasks: 638 total,   2 running, 634 sleeping,   0 stopped,   2 zombie
%Cpu0  : 10.8 us,  3.3 sy,  0.0 ni, 83.0 id,  0.3 wa,  0.0 hi,  2.6 si,  0.0 st
%Cpu1  :  8.9 us,  4.0 sy,  0.0 ni, 85.5 id,  0.3 wa,  0.0 hi,  1.3 si,  0.0 st
%Cpu2  : 13.1 us,  2.9 sy,  0.0 ni, 82.7 id,  0.3 wa,  0.0 hi,  1.0 si,  0.0 st
%Cpu3  :  8.2 us,  3.6 sy,  0.0 ni, 87.3 id,  0.0 wa,  0.0 hi,  1.0 si,  0.0 st
MiB Mem :  15731.1 total,    529.8 free,  12563.0 used,   2638.3 buff/cache
MiB Swap:  30720.0 total,  19096.5 free,  11623.5 used.   1740.3 avail Mem 
...

物理内存 16G 占用,SWAP 额外占用 11G 空间。这种问题通常重启便能解决:-),或者换个内存更大的笔记本(ThinkPad P15 P17 能够加到 128G 内存:-),但是草草地看看也没有找到我们喜欢的笔记本(电源太大、笔记本太重、不支持扩展钨、不支持 Intel RST RAID 技术),所以最后我们决定还是排查内存系统使用情况,分析各个进程的内存占用情况。从以往的经验看,不是 Chrome、Firfox 的问题,就是 GNOME Desktop 的问题,或者某些应用程序内存泄漏。但是本着严谨的态度,我们还是应该分析一番,不能总是靠着猜测和重启(当然,在有些情况下,解决问题为首要任务,因此重启也是一种重要手段,这并不冲突)。

该笔记将记录:在 Linux 中,如何分析进程内存使用情况(比如进程堆栈、共享内存、内存分布等等方面),以及相关问题处理(比如内存泄漏等等)。

解决方案

在这之前,我们需要学习几个内存使用情况的指标。

RSS (Resident Set Size)

RSS in ps, RES in top

这是进程占用的实际物理内存,是我们应该关注的指标,也是我们进程使用的指标。

但是这里存在两个问题:
1)RSS 其中包含进程共享库占用的内存:

这导致共享内存被重复计算:假如两个进程都使用相同的共享库,那么共享库占用的内存也会计算在各个进程的 RSS 中。

还会也会导致应用程序占用的内存偏大:尤其是在桌面环境中,例如 gedit 占用 54M 内存,而大部分内存被 libgnome libgtk 等等共享库占用,而 gedit 仅占用几兆内存。

2)写入 SWAP 的内存没有计算在内,这些也是进程占用的内存(只是不活动才写入 SWAP 中)

VMSize (virtual memory size)

VSZ in the ps, VIRT in top

该指标是 /proc/<PID>/map 所有指标的累计值。

因为现代操作系统都使用虚拟地址空间,只有少部分虚拟内存被映射到物理内存,所以这是最没用的参数(几乎不很少使用),完全能够忽略。

PSS (Proportional Set Size)

PSS 算是 RSS 的折中,该指标“平分”共享库占用的内存。假如,三个进程各自占用 5M 内存,此外他们使用占 6M 内存的相同共享库,那么每个进程的 PSS = 5 + 6 / 3 = 7M 内存。

但是这里依旧有两个问题:
1)如果三个进程中某个退出,剩余两个进程占用的内存会突然升高(因为被平均嘛:-),谁还不是年薪百万……)
2)SWAP 也属于进程占用的内存,但是 PSS 并未包含换入 SWAP 的内存大小;

USS (Uniq Set Size)

仅属于某进程、不与其他进程共享、在进程退出时将会被释放的内存。

总体的内存使用情况分析(ps、top)

ps

通过 ps 命令:

# ps aux | sort -nk 4 | tail -5
...

# ps -o pid,user,%mem,command --ppid 2 --pid 2 --deselect | sort -k 3 -h -r | head -n 10
...

# ps -o pid,user,%mem,comm --ppid 2 --pid 2 --deselect | sort -k 3 -h -r | head -n 10
12234 k4nz     13.2 java
22340 k4nz     11.8 chromium
 3754 k4nz      6.6 gnome-shell
22515 k4nz      4.7 java
 3417 k4nz      3.1 Xorg
  760 k4nz      2.4 python
22562 k4nz      1.6 chromium
26803 k4nz      1.4 chromium
25726 k4nz      1.4 slack
25586 k4nz      1.3 chrome

// 我们仅需要关注 %MEM(第三列),该列是 RSS 的百分比。因此 也存在 RSS 指标所具有的问题。

通过 ps 命令,按照用户使用情况进行排序:

for user in $(ps auxh | awk '{print $1}' | sort | uniq)
do
	stats="$stats\n"$(ps aux | grep -E "^$user" | awk 'BEGIN{total=0};{total+=$4};END{print total, $1}')
done

echo -e $stats | sort -k 1 -h -r

top

通过 top 命令,Shift+M 来按照 %MEM 排序 以快速定位使用内存较多的进程。

通过 top -U k4nz 来仅显示 k4nz 用户的进程。

这里我们不再深入展开,指标与 ps 是类似的,也存在相同的问题。

通过 pmap 命令

# pmap -p -X $(pidof gedit)
13794:   /usr/bin/gedit --gapplication-service
     Address Perm   Offset Device     Inode   Size   Rss   Pss Referenced Anonymous LazyFree ShmemPmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible Mapping
56280a7c8000 r--p 00000000  fd:00   5767537      4     4     4          4         0        0              0              0               0    0       0      0           0 /usr/bin/gedit
56280a7c9000 r-xp 00001000  fd:00   5767537      4     4     4          4         0        0              0              0               0    0       0      0           0 /usr/bin/gedit
56280a7ca000 r--p 00002000  fd:00   5767537      4     4     4          4         0        0              0              0               0    0       0      0           0 /usr/bin/gedit
56280a7cb000 r--p 00002000  fd:00   5767537      4     4     4          4         4        0              0              0               0    0       0      0           0 /usr/bin/gedit
56280a7cc000 rw-p 00003000  fd:00   5767537      4     4     4          4         4        0              0              0               0    0       0      0           0 /usr/bin/gedit
56280c239000 rw-p 00000000  00:00         0  14360 14104 14104      14104     14104        0              0              0               0    0       0      0           1 [heap]
...(略 800 行)
7f691d7e7000 r--p 00026000  fd:00   6165492      4     4     4          4         4        0              0              0               0    0       0      0           0 /usr/lib/x86_64-linux-gnu/ld-2.28.so
7f691d7e8000 rw-p 00027000  fd:00   6165492      4     4     4          4         4        0              0              0               0    0       0      0           0 /usr/lib/x86_64-linux-gnu/ld-2.28.so
7f691d7e9000 rw-p 00000000  00:00         0      4     4     4          4         4        0              0              0               0    0       0      0           1 
7fff031c6000 rw-p 00000000  00:00         0    132   100   100        100       100        0              0              0               0    0       0      0           1 [stack]
7fff031ec000 r--p 00000000  00:00         0     12     0     0          0         0        0              0              0               0    0       0      0           0 [vvar]
7fff031ef000 r-xp 00000000  00:00         0      8     4     0          4         0        0              0              0               0    0       0      0           0 [vdso]
                                            ====== ===== ===== ========== ========= ======== ============== ============== =============== ==== ======= ====== =========== 
                                            413984 47656 23884      47600     17032        0              0              0               0    0       0      0          54 KB 

从 Pss 列,我们能够看到 gedit 占用 23884KB 内存,远小于 Rss 列的 47656KB 内存(共享库还是占用很大比重的)。

虽然某些共享库会出现多次,但是其作用并不相同,权限为 r-x– 表示其为共享库的代码段,而权限为 rw— 表示其与运行程序相关的数据变量。

通过 smem 命令

# smem -t -k
  PID User     Command                         Swap      USS      PSS      RSS 
 1563 root     /usr/sbin/dnsmasq --conf-fi   316.0K        0     2.0K   320.0K 
 2666 root     /bin/sh /app/start.sh clien    96.0K     4.0K     4.0K     8.0K 
 1154 avahi    avahi-daemon: chroot helper   288.0K     4.0K     5.0K     1.0M 
 1645 root     /usr/sbin/dnsmasq --conf-fi   332.0K     4.0K     6.0K   916.0K 
17470 k4nz     /opt/XMind/xmind --type=zyg     7.5M     4.0K     7.0K   484.0K 
...
 3417 k4nz     /usr/lib/xorg/Xorg vt2 -dis   413.6M   292.2M   388.3M   488.8M 
25586 k4nz     /opt/google/chrome/chrome -    92.1M   417.3M   424.7M   466.5M 
22515 k4nz     /opt/pycharm/2019.1.3/jre64   299.4M   740.7M   740.8M   747.1M 
 3754 k4nz     /usr/bin/gnome-shell          274.0M   984.5M   990.1M  1011.1M 
22340 k4nz     /usr/lib/chromium/chromium    566.3M     1.7G     1.7G     1.8G 
12234 k4nz     /opt/openjdk/openjdk-15.0.1   264.8M     2.0G     2.0G     2.0G 
-------------------------------------------------------------------------------
  357 15                                       8.8G    12.3G    12.8G    17.2G

但是 smem 未显示进程占用内存总计,即 Swap + PSS 参数。

通过脚本统计

查看单个进程占用的内存:

# cat /proc/31768/smaps | grep -i pss |  awk '{Total+=$2} END {print Total/1024" MB"}'
56.4102 MB

# cat /proc/31768/smaps | grep -i rss |  awk '{Total+=$2} END {print Total/1024" MB"}'
58.7109 MB

最后总结

我们也发现,占用内存极高的是 Eclipse Chromium Slack Barrier 程序,目前我们仅能重启这些程序,好在不用再重启电脑:-)

接下来,我们开始学习 Memory Leak 相关的内容,来排查和定位这些应用程序。

参考文献

Showing memory usage in Linux by process and user | Network World
Accurately measuring memory usage – Passenger Library
Virtual Threads: Understanding memory usage on Linux
How to check memory usage per process in Linux | GoLinuxCloud