前言
问题真是一个接一个,做开发就是解决一个又一个问题吗?
像死机、内存泄漏这些问题很多时候是没有框架、设计或有了框架和设计但是团队没有统一遵循标准按着自己性子来导致的,统一的框架和设计也许会损失一定的灵活性,但是他会让你在编码的时候遵从一定的范式,且通过规范格式可以做到良好的自检查,例如将一个代码的实现分别放在A、B、C三个地方,A、B、C、分别干啥,接口是啥,A、B、C的范例等都在框架和设计中定义好了,每个编码人员按这个进行具体的开发编码工作,可以并行有序,甚至A、B、C进行不同的分工开发互不影响,比如A是解析配置的,B是做初始化的,C是业务处理主体。
对于应用开发,从框架、设计及编码质量这些工程角度避免低级错误,把开发的大部分时间用在算法、业务逻辑实现与优化等给客户带来良好体验与价值的地方,是一个软件团队需要努力的方向。
好吧,回到内存泄漏,这里指的是那些默默消耗了系统有限内存的操作,不仅仅指代码中申请而没有释放的内存,简单总结了下面几种情况进行检查和定位:
1,应用进程造成的内存泄漏
2,内核空间/或因应用进程的操作导致内核空间的内存泄漏
3,用户空间产生的临时文件(如日志等)造成的内存泄漏
应用进程造成的内存泄漏
1)采用ps或top命令进行观察
首先,我们习惯性的会看下那个进程在泄漏内存,我这里使用一个test_core_dump的测试进程,该进程每2秒钟会分配一个10000字节的空间,并作简单赋值(注意:如果仅malloc而不使用,编译器会优化,实际测试时将看不到内存泄漏的测试效果):
root@OpenWrt:/# ps
PID USER VSZ STAT COMMAND
14444 root 15984 S test_core_dump
省略不必要的信息,这里,我们主要看VSZ这个字段,这个字段表示该进程的虚拟内存大小,单位为KBytes,也就是此时我的测试进程已经占用近16M的虚拟内存空间,那是否意味着物理内存就已经被吃掉16M呢,这个我等下再讲。
或者我们通过top命令,也可以观察进程的VSZ字段,这里我当时没抓速据,所以就不放速据了,不管是top还是ps,重点是看VSZ字段的变化,当你发现VSZ字段一直在变化,则很明显这个进程有内存泄漏的问题,比如我现使用ps再看:
root@OpenWrt:/# ps
PID USER VSZ STAT COMMAND
14444 root 73884 S test_core_dump
我的测试进程已经占用近73M的虚拟地址空间。
2)查看/proc/meminfo
一般我们在发现有进程内存泄漏时,还会去查看一个文件,就是/proc/meminfo,看系统还有多少内存,比如我在上一步第一次ps时就去看了这个文件:
root@OpenWrt:/# cat /proc/meminfo
MemTotal: 125064 kB
MemFree: 28536 kB
Buffers: 8128 kB
Cached: 27844 kB
SwapCached: 0 kB
Active: 23004 kB
Inactive: 19996 kB
Active(anon): 10888 kB
Inactive(anon): 696 kB
Active(file): 12116 kB
Inactive(file): 19300 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 7092 kB
Mapped: 4648 kB
Shmem: 4556 kB
Slab: 36684 kB
SReclaimable: 5792 kB
SUnreclaim: 30892 kB
KernelStack: 704 kB
PageTables: 588 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 62532 kB
Committed_AS: 37800 kB
VmallocTotal: 1048372 kB
VmallocUsed: 12308 kB
VmallocChunk: 1020828 kB
看了系统还有MemFree: 28536 kB,约28M内存(我在系统启动完成时,就看了一次,大概是剩39M内存),那是不是这11M内存都是我的测试程序吃了呢?从第一次ps看到的虚拟内存16M来看,好像数据对不上?
不妨在第二次ps的时候,看下/etc/meminfo的信息:
root@OpenWrt:/# cat /proc/meminfo
MemTotal: 125064 kB
MemFree: 21936 kB
Buffers: 8128 kB
Cached: 27940 kB
SwapCached: 0 kB
Active: 28940 kB
Inactive: 19976 kB
Active(anon): 16808 kB
Inactive(anon): 692 kB
Active(file): 12132 kB
Inactive(file): 19284 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 12900 kB
Mapped: 4716 kB
Shmem: 4652 kB
Slab: 37360 kB
SReclaimable: 6392 kB
SUnreclaim: 30968 kB
KernelStack: 648 kB
PageTables: 644 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 62532 kB
Committed_AS: 94268 kB
VmallocTotal: 1048372 kB
VmallocUsed: 12308 kB
VmallocChunk: 1020828 kB
我们看到,第二次ps看到测试进程占用的虚拟内存为73M,而此时MemFree: 21936 kB,约为21M,也就是物理内存距上次的28M减少到21M,吃掉了7M,但虚拟内存是16M增长到73M,这明显不是一个换算关系,接下来我们看物理内存在哪里看:
3)查看物理内存占用,通过/proc/pid/stat或/proc/pid/statm(这里不能设置字体颜色,以粗体表示)
root@OpenWrt:/# cat /proc/14444/statm
21861 2332 202 1 0 20985 0
我们看到的21861表示VSZ,2332表示物理内存,这里的单位是页(page),我所用的系统当前页单位设置是4KB/页,所以可以得出:
VSZ = 21861 *4 = 87444KB,约87M(使用3)查看时,已距73M过了一段时间)
物理内存=2332 *4 = 9328,约9M,这时我们可以看到,基本可以和/proc/meminfo对的上了
或者,我们采用下面命令进行查看,同样可以看到该结果,这是下面文件参数太多,如果只是看内存,建议使用上个文件(注:下个文件中的89702400字段单位为Bytes而不是页,有些网上的文档解释成页,就误导了,而2332字段的单位是页)
root@OpenWrt:/# cat /proc/14444/stat
14444 (test_core_dump) S 496 14444 496 1089 29862 4194304 2488 0 0 0 36 14 0 0 20 0 1 0 7535179 89702400 2332 2147483647 4194304 4197032 2145364880 2145364392 1999211440 0 0 4096 0 2164588092 0 0 18 3 0 0 0 0 0 4262568 4262663 11354112 2145365870 2145365885 2145365885 2145365988 0
此时,通过上面的命令及文件的分析,基本可以定位或查找出应用进程的内存泄漏问题。
注:
1)ps和top命令很多网上的文档会说有VIRT、RES和SHR字段,但这可能是在PC端的linux,在嵌入式中,我遇到的仅有VSZ字段,相当于VIRT,所以要通过/proc/pid下的文件辅助
2)参考1):/proc/pid/stat字段说明(该文章用红色字体标明vsize的错误,赞)
3)参考2):进程的虚拟内存,物理内存,共享内存(包括statm文件格式说明),这里解释了虚拟内存和驻留内存(我理解实际使用的内存)的关系,就是上面VSZ和/proc/meminfo的数据关系
4)参考3):proc/meminfo 文件内存详解
内核/或因应用进程的操作导致内核空间的内存泄漏
排查内核空间的内存泄漏,这里主要争对slab来讲,尝试下面几步:
1)查看/proc/meminfo 中slab相关的字段:
root@OpenWrt:/# cat /proc/meminfo
MemTotal: 125064 kB
MemFree: 8536 kB
Buffers: 8128 kB
Cached: 28736 kB
SwapCached: 0 kB
Active: 20892 kB
Inactive: 21356 kB
Active(anon): 8628 kB
Inactive(anon): 692 kB
Active(file): 12264 kB
Inactive(file): 20664 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 5336 kB
Mapped: 4664 kB
Shmem: 3936 kB
Slab: 57380 kB
SReclaimable: 23040 kB
SUnreclaim: 34340 kB
KernelStack: 640 kB
PageTables: 552 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 62532 kB
Committed_AS: 23520 kB
VmallocTotal: 1048372 kB
VmallocUsed: 12308 kB
VmallocChunk: 1020828 kB
我们看到,MemFree: 8536 kB,约只剩8M内存,通过查看slab相关字段,Slab: 57380 kB我们看到约占用了57M空间,其中可回收的slab占,SReclaimable: 23040 kB,不可回收的slab占,SUnreclaim: 34340 kB,再看下半天前保留下来的meminfo(为了查找问题,提前保留的信息以作对比)
root@OpenWrt:/# cat /proc/meminfo
MemTotal: 125064 kB
MemFree: 21196 kB
Buffers: 8128 kB
Cached: 26456 kB
SwapCached: 0 kB
Active: 22968 kB
Inactive: 21528 kB
Active(anon): 10984 kB
Inactive(anon): 588 kB
Active(file): 11984 kB
Inactive(file): 20940 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 9948 kB
Mapped: 4680 kB
Shmem: 1660 kB
Slab: 42372 kB
SReclaimable: 11800 kB
SUnreclaim: 30572 kB
KernelStack: 624 kB
PageTables: 572 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 62532 kB
Committed_AS: 26704 kB
VmallocTotal: 1048372 kB
VmallocUsed: 12308 kB
VmallocChunk: 1020828 kB
可以看出,slab大约增加了15M内存的使用(57-42),而我们再仔细看,这其中SReclaimable约前后相差12M(23-11),这部分空间是可以回收的。既然这块内存变化较大,那我们重点看下slab的内存分配情况:
2)cat /proc/slabinfo, 这里信息太多,不全部放上来,我采集了两个速据做对比,这两个速据之间有一个动作,第3)步再讲:
有一个比较好的命令可以显示的比cat /proc/slabinfo更直观,但是我之前没有去了解,因为习惯性动作,直接cat文件,虽然一直觉得这个文件不是很直观,这个第4)步讲一下。我们来看这个数据,按照常规的逻辑,先找最大的那个数,或者我们惯性觉得最大的数就是异常的,在图1中我们看到dentry比较异常,共计153381个num_objs对象,每个对象136字节,占用空间约19M,我们看图2第二个异常kmalloc-128,共计124200个num_objs对象,每个对象128字节,占空间约15M。
通过分析内存占用较大的slab可以定位大概是哪里的问题,比如这两个占用内存比较大的slab,第一个dentry和文件操作相关,应该是应用层频繁的文件操作导致内核频繁申请dentry内存所致;第二个kmalloc-128是内核模块自己申请内存导致,这个可以首先排查自己私有模块用到128字节以内分配的调用,然后排查内核本身(如果这里有问题,内核本身的问题概率较少)。
3)尝试释放Slab占用的cache内存空间
虽然我们看到有两个slab占用较多,但是不能说这就是内存泄漏(或者也可以说是一种泄漏吧,只是不是因为kmalloc后没有kfree),也许是他就是要用到这么多内存,只是因为某种设计原因导致他过多和过快的占用了内存,影响到系统的稳定性。因为我们前面也看到,SReclaimable: 23040 kB有23M之多,这些是可回收和利用的,只是我们的系统还没有触发回收机制。
当前做一个简单的尝试,使用命令:
echo 2 > /proc/sys/vm/drop_caches
该命令释放dentries and inodes的可回收slab内存。
我们再来看,释放后的meminfo和slab:
root@OpenWrt:/# cat /proc/meminfo
MemTotal: 125064 kB
MemFree: 33912 kB
Buffers: 8128 kB
Cached: 27108 kB
SwapCached: 0 kB
Active: 20140 kB
Inactive: 20456 kB
Active(anon): 8616 kB
Inactive(anon): 692 kB
Active(file): 11524 kB
Inactive(file): 19764 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 5416 kB
Mapped: 4664 kB
Shmem: 3948 kB
Slab: 33716 kB
SReclaimable: 2936 kB
SUnreclaim: 30780 kB
我们看到,MemFree: 33912 kB,从8M提升到了33M,slab从57M降低到33M,差不多回收了20多M内存,基本吻合,以此也可进一步判断这些内存不是因为没有kfree进了黑洞,而是因为kfree后还没有被系统回收和利用。那么我看在看看slabinfo,看数据是否也吻合,我们可以参考图1与图2的对比:drop_caches后,dentry,共计7714个num_objs对象,每个对象136字节,占用空间约1M;kmalloc-128,共计97350个num_objs对象,每个对象128字节,占空间约11M。对比drop_caches前,相当于(19+15-1-11)=22M,和回收了20多M内存也基本吻合。但从这里可以得出经验:
1,kmalloc-128还是有嫌疑,这个还是可以通过排除私有内核模块来处理,比如在系统启动后,所有模块加载完成的第一时间看slabinfo,如果在11M左右,说明确实需要,没有泄漏的异常,如果这个数据越来越大,通过drop_caches也回收不了且回收后大于11M较多,那就要检查内核相关模块;
2,dentry内存如果是因为应用层导致,则基本是可回收的,因为不是内核本身的异常,所以这时需要优化应用层相关的程序避免这个问题。
4)slabtop命令
Slab是用于存放内核数据结构缓存,再通过slabtop命令查看这部分内存的使用情况,这个和查看slabinfo效果一样,但是比较直观,具体格式这里不做解释,和slabinfo差不多:
root@OpenWrt:/# slabtop
Active / Total Objects (% used) : 217931 / 232100 (93.9%)
Active / Total Slabs (% used) : 9664 / 9664 (100.0%)
Active / Total Caches (% used) : 97 / 199 (48.7%)
Active / Total Size (% used) : 37514.55K / 38973.26K (96.3%)
Minimum / Average / Maximum Object : 0.01K / 0.17K / 4096.00K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
96900 91608 94% 0.13K 3230 30 12920K kmalloc-128
54259 54259 100% 0.13K 1871 29 7484K dentry
10962 7956 72% 0.02K 54 203 216K ft_dic_binary_cache
8673 8660 99% 0.06K 147 59 588K buffer_head
8475 8175 96% 0.01K 25 339 100K ft_head_cache
5369 5215 97% 0.06K 91 59 364K sysfs_dir_cache
5010 4974 99% 0.25K 334 15 1336K kmalloc-256
2448 2409 98% 0.50K 306 8 1224K kmalloc-512
2424 2421 99% 1.00K 606 4 2424K kmalloc-1024
2175 2154 99% 0.02K 15 145 60K match_tree_feat_layer
2085 1783 85% 0.25K 139 15 556K skbuff_head_cache
1956 1918 98% 2.00K 978 2 3912K kmalloc-2048
1880 1700 90% 0.09K 47 40 188K vm_area_struct
1740 1677 96% 0.02K 12 145 48K ft_dic_tree_head_cache
1608 1556 96% 0.32K 134 12 536K inode_cache
1400 1400 100% 0.19K 70 20 280K filp
注:
1)参考一篇比较好的linux内存管理文章:详细讲解从用户空间申请内存到内核如何为其分配内存的过程,我把上面讲的内容放在一起,大概就是一个这样的图:
2)参考:Linux Malloc分析-从用户空间到内核空间
3)drop_caches相关参考,及设置内存回收的条件和时机:Linux服务器Cache占用过多内存导致系统内存不足问题的排查解决,Linux服务器Cache占用过多内存导致系统内存不足问题的排查解决(续)
用户空间产生的临时文件(如日志等)造成的内存泄漏
这个其实开发人员自己心里可能是清楚的,因为这些临时文件基本是自己产生的,但因为缺乏统一设计(比如统一使用syslog)和没上心,可能把这个问题带到测试或客户环境,可以通过命令du -sh查看: