linux内存统计之谁吃了我的内存

写该篇文章的用意不在于怎么解决某个问题,而是希望表达出因这次线上问题而引发出解决问题的思路。

问题背景

公司内部的一个license服务器,部署了一套apache+mod_wsgi+python服务,该服务用户量很小,但是内存使用量却很大,其中有一个比较奇怪的现象,就是我通过top命令看到的进程使用内存很小,但是free命令看到的使用内存确很大,下面我会先引出问题然后说一下我的解题思路

解题一

首先free命令查看使用和空闲内存,如下:

$ free -m
             total       used       free     shared    buffers     cached
Mem:          1877       1736        141          0          6         23
-/+ buffers/cache:       1706        170
Swap:            0          0          0

可能你已经发现内存使用达到了1736m(这不是重点),这里说一下我们的系统使用用户就只有几个人,甚至并发都没有,正常情况不可能使用1个多G的内存。
接下来我们top +M命令看一下进程用量:

top - 09:45:01 up 1 day, 23:59,  2 users,  load average: 0.00, 0.00, 0.00
Tasks: 112 total,   2 running, 110 sleeping,   0 stopped,   0 zombie
Cpu(s):  1.8%us,  1.5%sy,  0.0%ni, 96.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1922620k total,  1778904k used,   143716k free,     6900k buffers
Swap:        0k total,        0k used,        0k free,    23564k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                               
 2639 apache    20   0 1722m 265m 3588 S  0.0 14.1   2:20.72 httpd                                                                                                                                 
 6246 apache    20   0 1530m  56m 3576 S  0.0  3.0   2:40.17 httpd                                                                                                                                 
 2564 apache    20   0 1530m  42m 3616 S  0.3  2.3   1:35.79 httpd                                                                                                                                 
 1641 mysql     20   0  818m  31m 2448 S  0.0  1.7   8:54.25 mysqld                                                                                                                                
 8895 apache    20   0  881m  29m 6496 S  0.0  1.6   0:25.66 httpd                                                                                                                                 
27060 root      20   0 28824 3052 2240 S  0.3  0.2   0:03.14 sshd                                                                                                                                  
21110 root      20   0 28716 2836 2176 S  0.0  0.1   0:00.12 sshd                                                                                                                                  
27460 root      20   0 28444 2692 2176 S  0.0  0.1   0:00.04 sshd                                                                                                                                  
 1179 root      20   0  165m 2004 1132 S  0.0  0.1   2:30.19 vmtoolsd                                                                                                                              
27062 root      20   0  105m 1996 1536 S  0.0  0.1   0:00.08 bash                                                                                                                                  
27075 root      20   0  105m 1984 1544 S  0.0  0.1   0:00.00 bash                                                                                                                                  
 1312 root      20   0 88788 1956  488 S  0.0  0.1   0:16.80 httpd                                                                                                                                 
27468 root      20   0 27732 1776 1352 S  0.0  0.1   0:00.00 sftp-server                                                                                                                           
21133 root      20   0 27732 1708 1236 S  0.0  0.1   0:00.00 sftp-server                                                                                                                           
21127 root      20   0 27732 1684 1236 S  0.0  0.1   0:00.02 sftp-server                                                                                                                           
21114 root      20   0 27732 1636 1236 S  0.0  0.1   0:00.02 sftp-server 

有心人已经发现了问题,what append? 我的内存哪里去了,通过top命令观察进程使用的内存量最多也就400多m,但是free明明使用总量1736m。
注意: 有经验的人可能会说linux系统会使用buffers/cache来提高处理性能而且这部分内存是可以释放的,很多人都知道大名鼎鼎的《Linux ate my linux》网站这里附上网址:https://www.linuxatemyram.com/,这篇网站介绍的也大都是buffers、cache占用系统内存以提高性能,但是这里请注意看我的free命令结果,buffers为6m,cache为23m,used和top进程相差还是太大了。

太奇怪了,谁吃了我的内存!!!

解题二

上面我们已经知道,既然不是buffers/cache的问题,那内存哪里去了?会不会是free命令或者top命令的问题呢,既然free命令可以统计内存那她是怎么统计的呢。top又是怎么统计每个进程的内存呢,我们先来分析top命令,其实top命令是读取的/proc/$PID/statm文件,/proc/$PID/statm文件用linux官方的描述为:Process memory status information详细信息请参考连接https://www.kernel.org/doc/html/latest/filesystems/proc.html,
/proc/$PID/statm文件内容如下:

$ cat /proc/1449/statm 
2762 258 75 36 0 195 0

该文件包含了7个字段,每个字段的含义可参考下表:

FieldContent
sizetotal program size (pages)(same as VmSize in status)
residentsize of memory portions (pages)(same as VmRSS in status)
sharednumber of pages that are shared(i.e. backed by a file, same as RssFile+RssShmem in status)
trsnumber of pages that are ‘code’(not including libs; broken, includes data segment)
lrsnumber of pages of library(always 0 on 2.6)
drsnumber of pages of data/stack(including libs; broken, includes library text)
dtnumber of dirty pages(always 0 on 2.6)

上面的七个字段我们要特别留意其中的第二个字段也就是resident, 该字段在linux中有一个专业的术语叫RSS(Resident Set Size)翻译为中文就是:驻留集大小,其实使用过ps命令的同学应该注意到过,ps命令会输出进程的两个字段:RSS,VSZ,如下第五和第六列:

$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  19232  1504 ?        Ss   Jul28   0:01 /sbin/init
root         2  0.0  0.0      0     0 ?        S    Jul28   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        S    Jul28   0:00 [migration/0]

上面ps命令结果中的RSS字段就是对应的statm文件的第二个字段,细心的同学可能又发现了问题,不对啊?为什么statm文件的结果和ps命令的结果不一样的,这是因为statm文件的字段单位是page,我们知道linux是使用内存页来分配和寻址的,所以这里我们应该把statm结果的resident字段乘以内存页的大小(默认为4kb,也可以使用getconf PAGESIZE命令查看)。
关于RSS和VSZ的详细介绍这里不做过多解释,可以参考我的这篇文章,里面解释的很清楚:linux ps命令VSZ和RSS内存使用的区别

这里我们姑且认为RSS也就是/proc/1449/statm 文件的第二个字段就是我们程序使用的实际物理内存(其实也差不多了),也是top命令读取进程内存的大小,看到这里我们应该可以想到那我是不是只要统计/proc目录下所有进程的RSS内存信息不就可以知道所有进程占用的内存了吗?答案是肯定的,我写了下面的脚本用来统计系统所有进程的内存和,可以用作参考:

$ cat RSS.sh
#!/bin/bash                                                                                                               
for PROC in `ls  /proc/|grep "^[0-9]"`
do
  if [ -f /proc/$PROC/statm ]; then
      TEP=`cat /proc/$PROC/statm | awk '{print ($2)}'`
      RSS=`expr $RSS + $TEP`
  fi
done
RSS=`expr $RSS \* 4` # 注意这里要乘以4(内存页大小)
echo $RSS"KB" 

那么上面我们已经可以统计出来系统中进程所占用的总内存了,但是这还不够,其实我们还可以使用一个工具来统计系统所占用的内存:nmon,该工具可以很直观的统计出系统内存被什么给占用,如下:

$ nmon

在这里插入图片描述
上图圈起来的部分,我们可以看到还有两个东西占用了内存:Slab和PageTables,这两个又是什么东西?由于篇幅原因我这里只做一个简单的介绍,
简单的说内核为了高性能每个需要重复使用的对象都会有个池,这个slab池会cache大量常用的对象,所以会消耗大量的内存。运行命令:

$ slabtop
 Active / Total Objects (% used)    : 90854 / 97978 (92.7%)
 Active / Total Slabs (% used)      : 5073 / 5073 (100.0%)
 Active / Total Caches (% used)     : 97 / 181 (53.6%)
 Active / Total Size (% used)       : 24085.29K / 25296.83K (95.2%)
 Minimum / Average / Maximum Object : 0.02K / 0.26K / 4096.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                   
 18256  18124  99%    0.03K    163      112       652K size-32
  8964   8950  99%    0.14K    332       27      1328K sysfs_dir_cache
  8319   8010  96%    0.06K    141       59       564K size-64
  8200   7510  91%    0.19K    410       20      1640K dentry
  7632   7573  99%    0.07K    144       53       576K selinux_inode_security
  4580   4577  99%    0.19K    229       20       916K size-192
  4399   4286  97%    0.07K     83       53       332K Acpi-Operand
  4275   3675  85%    0.20K    225       19       900K vm_area_struct
  4116   4101  99%    0.58K    686        6      2744K inode_cache
  3619   2877  79%    0.05K     47       77       188K anon_vma_chain
  3312   3224  97%    0.04K     36       92       144K Acpi-Namespace
  2516   1152  45%    0.10K     68       37       272K buffer_head
  2212   1767  79%    0.55K    316        7      1264K radix_tree_node
  1932   1669  86%    0.04K     21       92        84K anon_vma
  1560   1328  85%    0.19K     78       20       312K filp
  1500   1460  97%    0.12K     50       30       200K size-128
  1020    992  97%    1.00K    255        4      1020K size-1024
   996    887  89%    0.62K    166        6       664K proc_inode_cache
   920    904  98%    0.04K     10       92        40K dm_io
   864    860  99%    0.02K      6      144        24K dm_target_io
   768    757  98%    0.50K     96        8       384K size-512
   640    634  99%    0.78K    128        5       512K shmem_inode_cache
   588    557  94%    1.00K    147        4       588K ext4_inode_cache
   468    456  97%    2.00K    234        2       936K size-2048
   440    375  85%    0.19K     22       20        88K cred_jar
   390    281  72%    0.12K     13       30        52K pid
   360    324  90%    0.25K     24       15        96K skbuff_head_cache
   340    283  83%    0.11K     10       34        40K task_delay_info
   261    256  98%    2.59K     87        3       696K task_struct
   240    118  49%    0.08K      5       48        20K blkdev_ioc
   238    233  97%    0.53K     34        7       136K idr_layer_cache
   234    234 100%    4.00K    234        1       936K size-4096
   225    214  95%    0.81K     25        9       200K task_xstate

从图我们可以看出各种对象的大小和数目,遗憾的是没有告诉我们slab消耗了多少内存。
我们自己来算下好了:

$ echo `cat /proc/slabinfo |awk 'BEGIN{sum=0;}{sum=sum+$3*$4;}END{print sum/1024/1024}'` MB
31.6606 MB

好吧,把每个对象的数目*大小,再累加,我们就得到了总的内存消耗量:31M

那么PageTables呢?
简单来说是linux用于记录和管理虚拟内存地址的,这是一个硬性开销,具体可自行搜索了解
好吧,知道是干嘛的啦,管理这些物理页面的硬开销,那么具体是多少呢?如下命令:

$ echo `grep PageTables /proc/meminfo | awk '{print $2}'` KB
58052 KB

通过上面的分析,我对linux系统内存的消耗做了一下总结:

  1. 进程消耗。
  2. slab消耗
    3.pagetable消耗。
    我们把三种消耗汇总下和free出的结果比对,我汇总成了一下脚本,用来统计三种内存消耗并和free命令做了对比:
$ cat cm.sh
#!/bin/bash
for PROC in `ls /proc/|grep "^[0-9]"`
do
  if [ -f /proc/$PROC/statm ]; then
      TEP=`cat /proc/$PROC/statm | awk '{print ($2)}'`
      RSS=`expr $RSS + $TEP`
  fi
done
RSS=`expr $RSS \* 4`
PageTable=`grep PageTables /proc/meminfo | awk '{print $2}'`
SlabInfo=`cat /proc/slabinfo |awk 'BEGIN{sum=0;}{sum=sum+$3*$4;}END{print sum/1024/1024}'`

echo $RSS"KB", $PageTable"KB", $SlabInfo"MB"
printf "rss+pagetable+slabinfo=%sMB\n" `echo $RSS/1024 + $PageTable/1024 + $SlabInfo|bc`
free -m

我们运行一下,看下结果:

$ ./cm.sh 
357528KB, 4664KB, 28.5003MB
rss+pagetable+slabinfo=381.5003MB
             total       used       free     shared    buffers     cached
Mem:          3833       2991        841          0         20        102
-/+ buffers/cache:       2868        964
Swap:         4031         13       4018

上面的结果不需要太关注我的内存总大小,因为我后来加了内存,但是现象还是一样的现象。
free结果说使用了2868m, 我们的cm脚本中的rss+pagetable+slabinfo=381.5003MB,有没有被吓到,rss+slab+pagetable一共才用了381m,但是free统计却使用了高达2868m那我另外的内存被用在哪里了?

解题三

其实通过上面的分析我们已经可以知道cm.sh脚本统计的内存应该是没有问题了,那我们应该想到另外一个问题:那是不是free命令统计的有问题呢?free命令中的内存信息从哪里来的呢?其实free命令是读取的/proc/meminfo文件,关于/etc/meminfo文件中的字段含义可以查看这个链接:https://www.howtouselinux.com/post/linux-memory-metrics-proc-meminfo
那么我们来对比下cm.sh脚本的结果和meminfo中内存信息:

$ ./cm.sh 
1367988KB, 6880KB, 27.7792MB
rss+pagetable+slabinfo=1368.7792MB
             total       used       free     shared    buffers     cached
Mem:          3833       2694       1138          0          1         56
-/+ buffers/cache:       2637       1195
Swap:         4031         33       3998

$ cat /proc/meminfo 
MemTotal:        3925040 kB
MemFree:         1166624 kB
Buffers:            1708 kB
Cached:            57548 kB
SwapCached:         2936 kB
···

上面我截取了meminfo文件中的关键信息,其他信息省略了,通过对比我们发现rss+pagetable_slabinfo=1368m,而free命令的used为2637,且free命令取meminfo文件内存数据也没有错,既然free命令没有错,统计的rss+pagetable+slabinfo内存也没有错,那meminfo命令中的MemFree为什么这么少呢?这个时候我们就想到了是不是meminfo文件中的MemFree统计有问题?
其实这里有引出了另外一个问题,linux对内存分配的统计是怎么样的,也就是linux会把所有分配出去的内存进行统计吗?答案是否定的,其实linux kernal动态分配的内存就有一部分没有计入/proc/meminfo文件中。
以下部分截取自另一篇文章:https://blog.csdn.net/whbing1471/article/details/105468139
我们知道,Kernel的动态内存分配通过以下几种接口:

alloc_pages/__get_free_page: 以页为单位分配
vmalloc: 以字节为单位分配虚拟地址连续的内存块
slab allocator
kmalloc: 以字节为单位分配物理地址连续的内存块,它是以slab为基础的,使用slab层的general caches — 大小为2^n,名称是kmalloc-32、kmalloc-64等(在老kernel上的名称是size-32、size-64等)。
通过slab层分配的内存会被精确统计,可以参见/proc/meminfo中的slab/SReclaimable/SUnreclaim;
通过vmalloc分配的内存也有统计,参见/proc/meminfo中的VmallocUsed 和 /proc/vmallocinfo(下节中还有详述);

而通过alloc_pages分配的内存不会自动统计,除非调用alloc_pages的内核模块或驱动程序主动进行统计,否则我们只能看到free memory减少了,但从/proc/meminfo中看不出它们具体用到哪里去了。比如在VMware guest上有一个常见问题,就是VMWare ESX宿主机会通过guest上的Balloon driver(vmware_balloon module)占用guest的内存,有时占用得太多会导致guest无内存可用,这时去检查guest的/proc/meminfo只看见MemFree很少、但看不出内存的去向,原因就是Balloon driver通过alloc_pages分配内存,没有在/proc/meminfo中留下统计值,所以很难追踪。

总结

通过上面的分析,我们知道MemFree不知去向的一个原因就是Balloon driver通过alloc_pages抢走了内存,然而使用balloon driver的最大可能就是vmware esx服务,后来联系了公司的私有云维护人员协助调研发现,公司确实使用了vmware esx服务,而且其中一台host内存已经接近瓶颈,把我们使用的机器切到了别的host上问题解决🤦‍♂️🤦‍♂️🤦‍♂️,是不是感觉整个人都傻了。这里提供两个文章可以供大家了解一下vmware esx服务的内存共享机制:
http://blog.sina.com.cn/s/blog_5144081e0100wv80.html
https://blog.51cto.com/qq694157416/1345680

最终的结果很让人意外,但本篇文章希望提供一个内存分析的解题思路,授人以鱼不如授人以渔!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值