准备做网站迁移的,结果上服务器发现8G内存基本都跑完了,这台服务器只跑了php和nginx,进程数nginx只开了8个,php只开了66个,很奇怪,8G内存都被什么程序占用了?

查看内存:

具体怎么查看内存这张图说明的很清楚:

wKiom1RbIHXDg5DOAAIWtQPbHNE557.jpg

如果图不清晰请参考:http://www.redbooks.ibm.com/redpapers/pdfs/redp4285.pdf P46-47)

wKioL1RbFkqR5SynAAByLZ8zOOc775.jpg


wKiom1RbF3vD7FLtAAGrg7qDbD4372.jpg


清除buffer/cache的方法:

vm.drop_caches = 3


查看php和nginx分别占用的内存大小:1433+229+21 = 1683M

wKioL1RbFwjQpgxsAAEldCcx7oo839.jpg

还有查看RSS内存的方法:

[root@test ~]# ps -eo pid,args,rss|grep php-fpm|grep -v grep|awk '{a+=$NF}END{print a}'

1468048

使用如下脚本可以查看:

#/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`

echo $RSS"KB"


这个就奇怪了,free -m出来的内存跟实际应用程序占用的内存差5G,这5G内存跑哪去了?

查了相关资料发现是系统内存管理的一个slab程序占用了,如下,可以看到slab占用了5G左右的内存。

wKiom1RbGNTDO-3VAAHLGuWoST0212.jpg


介绍下nmon这个工具,它对内存的使用显示比较直观,附件可以直接下载该工具!

直接运行[root@test ~]# ./nmon_x86_64_rhel4

跟top使用方法类似,M表示内存,N表示network,D表示disk I/O

wKioL1RbIfnz_SHlAAEA_eBh4RI680.jpg


那个该死的slab是什么呢? 那个PageTables又是什么呢?

简单的说内核为了高性能每个需要重复使用的对象都会有个池,这个slab池会cache大量常用的对象,所以会消耗大量的内存。

slab是Linux操作系统的一种内存分配机制。其工作是针对一些经常分配并释放的对象,如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。而slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免这些内碎片。slab分配器并不丢弃已分配的对象,而是释放并把它们保存在内存中。当以后又要请求新的对象时,就可以从内存直接获取而不用重复初始化。

具体为:

采用伙伴算法分配内存时,每次至少分配一个页面。但当请求分配的内存大小为几十个字节或几百个字节时应该如何处理?如何在一个页面中分配小的内存区,小内存区的分配所产生的内碎片又如何解决?

   Linux2.0采用的解决办法是建立了13个空闲区链表,它们的大小从32字节到132056字节。从Linux2.2开始,MM的开发者采用了一种叫做slab的分配模式,该模式早在1994年就被开发出来,用于Sun Microsystem Solaris 2.4操作系统中。Slab的提出主要是基于以下考虑:

1)    内核对内存区的分配取决于所存放数据的类型。例如,当给用户态进程分配页面时,内核调用get_free_page()函数,并用0填充这个页面。 而给内核的数据结构分配页面时,事情没有这么简单,例如,要对数据结构所在的内存进行初始化、在不用时要收回它们所占用的内存。因此,Slab中引入了对象这个概念,所谓对象就是存放一组数据结构的内存区,其方法就是构造或析构函数,构造函数用于初始化数据结构所在的内存区,而析构函数收回相应的内存区。但为了便于理解,你也可以把对象直接看作内核的数据结构。为了避免重复初始化对象,Slab分配模式并不丢弃已分配的对象,而是释放但把它们依然保留在内存中。当以后又要请求分配同一对象时,就可以从内存获取而不用进行初始化,这是在Solaris 中引入Slab的基本思想。

实际上,Linux中对Slab分配模式有所改进,它对内存区的处理并不需要进行初始化或回收。出于效率的考虑,Linux并不调用对象的构造或析构函数,而是把指向这两个函数的指针都置为空。Linux中引入Slab的主要目的是为了减少对伙伴算法的调用次数。

2)      实际上,内核经常反复使用某一内存区。例如,只要内核创建一个新的进程,就要为该进程相关的数据结构(task_struct、打开文件对象等)分配内存区。当进程结束时,收回这些内存区。因为进程的创建和撤销非常频繁,因此,Linux的早期版本把大量的时间花费在反复分配或回收这些内存区上。从Linux2.2开始,把那些频繁使用的页面保存在高速缓存中并重新使用。

3)      可以根据对内存区的使用频率来对它分类。对于预期频繁使用的内存区,可以创建一组特定大小的专用缓冲区进行处理,以避免内碎片的产生。对于较少使用的内存区,可以创建一组通用缓冲区(如Linux2.0中所使用的2的幂次方)来处理,即使这种处理模式产生碎片,也对整个系统的性能影响不大。

4)      硬件高速缓存的使用,又为尽量减少对伙伴算法的调用提供了另一个理由,因为对伙伴算法的每次调用都会“弄脏”硬件高速缓存,因此,这就增加了对内存的平均访问次数。

     Slab分配模式把对象分组放进缓冲区(尽管英文中使用了Cache这个词,但实际上指的是内存中的区域,而不是指硬件高速缓存)。因为缓冲区的组织和管理与硬件高速缓存的命中率密切相关,因此,Slab缓冲区并非由各个对象直接构成,而是由一连串的“大块(Slab)”构成,而每个大块中则包含了若干个同种类型的对象,这些对象或已被分配,或空闲,如图6.12所示。一般而言,对象分两种,一种是大对象,一种是小对象。所谓小对象,是指在一个页面中可以容纳下好几个对象的那种。例如,一个inode结构大约占300多个字节,因此,一个页面中可以容纳8个以上的inode结构,因此,inode结构就为小对象。Linux内核中把小于512字节的对象叫做小对象。实际上,缓冲区就是主存中的一片区域,把这片区域划分为多个块,每块就是一个Slab,每个Slab由一个或多个页面组成,每个Slab中存放的就是对象。

更多slab内存分配机制可以参考:http://oss.org.cn/kernel-book/ch06/6.3.3.htm


slab有个类似top的命令,可以看到系统中slab的对象

wKioL1RbGn3jughGAAO_NbgM94U143.jpg

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

[root@localhost ~]# echo `cat /proc/slabinfo |awk 'BEGIN{sum=0;}{sum=sum+$3*$4;}END{print sum/1024/1024}'` MB

5430.34 MB


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

那么PageTables呢? 

你还没有计算page tables的大小,还有struct page也有一定的大小(每个页一个,64bytes),如果是2.6.32的话,每个页还有一个page_cgroup(32bytes),也就是说内存大小的2.3%(96/4096)会被内核固定使用的
struct page是系统boot的时候就会根据内存大小算出来分配出去的,18内核是1.56%左右,32内核由于cgroup的原因会在2.3%

管理这些物理页面的硬开销,那么具体是多少呢?

[root@localhost ~]# echo `grep PageTables /proc/meminfo | awk '{print $2}'` KB

40104 KB

通过slabtop我们看到Linux系统中有大量的dentry占用内存,那dentry是什么呢?

首先,我们知道inode对应于物理磁盘上的具体对象,而dentry是一个内存实体,其中的d_inode成员指向对应的inode,故可以把dentry看成是Linux文件系统中某个索引节点(inode)的链接,这个索引节点可以是文件,也可以是目录。而dentry是目录项高速缓存,是Linux为了提高目录项对象的处理效率而设计的,它记录了目录项到inode的映射关系。

小结下内存的去向主要有3个:1. 进程消耗。 2. slab消耗 3.pagetable消耗。


用strace工具分析

当前服务器是web集群的节点,主要跑的是php,首先想到了php相关的工作进程,strace一下php的worker进程发现其中有非常频繁的stat系统调用发生,而且stat的文件总是新的文件名,每次用户连接都创建一次会话。

wKioL1RbHIiwTSx2AAWvWKP39XI155.jpg


进一步观察到php的worker进程会在本地目录下频繁的创建、打开会话文件,每秒钟一个新的文件名:

strace -fp 29499 -e trace=open,stat,close,unlink

wKioL1RbHYODLwVXAAK6k_AMUEY845.jpg

总结:php进程频繁的文件io操作,导致了dentry占用了系统太多的内存资源。


系统的自动slab缓存回收

在slab缓存中,对象分为SReclaimable(可回收)和SUnreclaim(不可回收),而在系统中绝大多数对象都是可回收的。内核有一个参数,当系统内存使用到一定量的时候,会自动触动回收操作。

内核参数:

vm.min_free_kbytes = 67584

1)代表系统所保留空闲内存的最低限。

在系统初始化时会根据内存大小计算一个默认值,计算规则是:

  min_free_kbytes = sqrt(lowmem_kbytes * 16) = 4 * sqrt(lowmem_kbytes)(注:lowmem_kbytes即可认为是系统内存大小)

另外,计算出来的值有最小最大限制,最小为128K,最大为64M。

可以看出,min_free_kbytes随着系统内存的增大不是线性增长,因为随着内存的增大,没有必要也线性的预留出过多的内存,能保证紧急时刻的使用量便足矣。

2)min_free_kbytes的主要用途是计算影响内存回收的三个参数 watermark[min/low/high]

(1) watermark[high] > watermark [low] > watermark[min],各个zone各一套

(2)在系统空闲内存低于 watermark[low]时,开始启动内核线程kswapd进行内存回收(每个zone一个),直到该zone的空闲内存数量达到watermark[high]后停止回收。如果上层申请内存的速度太快,导致空闲内存降至watermark[min]后,内核就会进行direct reclaim(直接回收),即直接在应用程序的进程上下文中进行回收,再用回收上来的空闲页满足内存申请,因此实际会阻塞应用程序,带来一定的响应延迟,而且可能会触发系统OOM。这是因为watermark[min]以下的内存属于系统的自留内存,用以满足特殊使用,所以不会给用户态的普通申请来用。

(3)三个watermark的计算方法:

 watermark[min] = min_free_kbytes换算为page单位即可,假设为min_free_pages。(因为是每个zone各有一套watermark参数,实际计算效果是根据各个zone大小所占内存总大小的比例,而算出来的per zone min_free_pages)

 watermark[low] = watermark[min] * 5 / 4

 watermark[high] = watermark[min] * 3 / 2

所以中间的buffer量为 high - low = low - min = per_zone_min_free_pages * 1/4。因为min_free_kbytes = 4* sqrt(lowmem_kbytes),也可以看出中间的buffer量也是跟内存的增长速度成开方关系。

(4)可以通过/proc/zoneinfo查看每个zone的watermark

例如:

Node 0, zone      DMA

pages free     3960

       min      65

       low      81

       high     97

3)min_free_kbytes大小的影响

min_free_kbytes设的越大,watermark的线越高,同时三个线之间的buffer量也相应会增加。这意味着会较早的启动kswapd进行回收,且会回收上来较多的内存(直至watermark[high]才会停止),这会使得系统预留过多的空闲内存,从而在一定程度上降低了应用程序可使用的内存量。极端情况下设置min_free_kbytes接近内存大小时,留给应用程序的内存就会太少而可能会频繁地导致OOM的发生。

min_free_kbytes设的过小,则会导致系统预留内存过小。kswapd回收的过程中也会有少量的内存分配行为(会设上PF_MEMALLOC)标志,这个标志会允许kswapd使用预留内存;另外一种情况是被OOM选中杀死的进程在退出过程中,如果需要申请内存也可以使用预留部分。这两种情况下让他们使用预留内存可以避免系统进入deadlock状态。

参考:http://kernel.taobao.org/index.php/Kernel_Documents/mm_sysctl

最终查明,slab cache占用过多属于正常问题,并且当内存到达系统最低空闲内存限制的话,会自动触发kswapd进程来回收内存,属于正常现象。

注:测了一下,当调整完min_free_kbytes值大于系统空闲内存后,kswapd进程的确从休眠状态进入运行态,开始回收内存。

根据我们跑的php服务做出如下调整:

vm.vfs_cache_pressure = 200

该文件表示内核回收用于directory和inode cache内存的倾向;缺省值100表示内核将根据pagecache和swapcache,把directory和inode cache保持在一个合理的百分比;降低该值低于100,将导致内核倾向于保留directory和inode cache;增加该值超过100,将导致内核倾向于回收directory和inode cache。

所以加快kswapd内存的回收。