问题描述
问题现象很简单,系统内存使用情况很高,如下命令输出所示:
# 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 其中包含进程共享库占用的内存:
还会也会导致应用程序占用的内存偏大:尤其是在桌面环境中,例如 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