在应用程序的老化测试时发现,系统被kill掉,但是从kill掉的内核打印信息,没有发现太大的问题。
[411371.892812] ntpd invoked oom-killer: gfp_mask=0x27000c0(GFP_KERNEL_ACCOUNT|__GFP_NOTRACK), nodemask=0, order=1, oom_score_adj=0
[411371.911869] COMPACTION is disabled!!!
[411371.916076] CPU: 0 PID: 1871 Comm: ntpd Not tainted 4.9.118 #1168
[411371.922965] Hardware name: xxxx
[411371.926972] [] (unwind_backtrace) from [] (show_stack+0x10/0x14)
[411371.935713] [] (show_stack) from [] (dump_stack+0x70/0x8c)
[411371.943874] [] (dump_stack) from [] (dump_header.constprop.3+0x7c/0x1b4)
[411371.953388] [] (dump_header.constprop.3) from [] (oom_kill_process+0xac/0x438)
[411371.963481] [] (oom_kill_process) from [] (out_of_memory+0x358/0x400)
[411371.972704] [] (out_of_memory) from [] (__alloc_pages_nodemask+0x954/0xa0c)
[411371.982509] [] (__alloc_pages_nodemask) from [] (copy_process.part.3+0xf8/0x12ac)
[411371.992891] [] (copy_process.part.3) from [] (_do_fork+0xa8/0x33c)
[411372.001820] [] (_do_fork) from [] (sys_vfork+0x20/0x28)
[411372.009682] [] (sys_vfork) from [] (ret_fast_syscall+0x0/0x48)
[411372.021360] [ pid ] uid tgid total_vm rss nr_ptes nr_pmds swapents oom_score_adj name
[411372.031093] [ 1133] 0 1133 559 373 6 0 0 -1000 ubusd
[411372.041688] [ 1142] 0 1142 480 92 4 0 0 0 askfirst
[411372.061911] [ 1290] 0 1290 560 341 5 0 0 -1000 logd
[411372.071568] [ 1292] 0 1292 571 110 6 0 0 0 logread
[411372.084341] [ 1333] 0 1333 9234 366 9 0 0 0 adbd
[411372.094664] [ 1833] 0 1833 619 424 5 0 0 -1000 netifd
[411372.108989] [ 1871] 0 1871 782 421 5 0 0 0 ntpd
[411372.120958] [ 1916] 0 1916 730 109 5 0 0 0 adb_shell
[411372.131689] [ 1918] 0 1918 761 488 6 0 0 0 sh
[411372.141524] [ 1928] 0 1928 730 109 6 0 0 0 exe
[411372.151432] [ 1933] 0 1933 8837 1046 13 0 0 0 test
[411372.162027] Out of memory: Kill process 1933 (test) score 8 or sacrifice child
[411372.170821] Killed process 1933 (test) total-vm:35348kB, anon-rss:2428kB, file-rss:1756kB, shmem-rss:0kB
从这里来看,并没有发现test进程存在内存占用过大的原因,测试程序本身需要使用的内存大小刚好与rss的大小差不多,同时检查确认程序流程,不存在内存泄露,但是为什么程序还是被kill掉呢?
程序没有内存泄露,但是为什么还是被kill掉呢?
一般的,都是有应用程序向系统申请内存,但是系统发现剩余的内存大小无法满足当前的申请,进行一系列的操作之后还是无法满足,将会选择最合适的程序将其kill,这样系统将可以回收它的内存,从而满足系统中其他进程的内存需求。所以,程序被kill掉,并不一定说该程序有内存泄露,只是说当系统内存被kill时,它最适合被kill。
那么,程序被kill掉的具体原因是什么呢?
在程序被kill之前,可以查看进程占用的内存信息,看看进程是否存在内存泄露:
cat /proc/PID/status
其中部分信息如下:
VmPeak: 3068 kB
VmSize: 3068 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 612 kB
VmRSS: 612 kB
我们主要查看VmRSS的大小是否逐渐在增大,如果该值逐渐增大,很大可能是程序存在内存泄露。但是在test测试中,程序的该值并没有很明显的变化,所以转向系统内存信息。
每隔一段时间,查看系统内存的信息,操作如下:
root@Linux: /# cat /proc/meminfo
MemTotal: 493184 kB
MemFree: 442572 kB
MemAvailable: 452300 kB
Buffers: 3424 kB
Cached: 3224 kB
SwapCached: 0 kB
Active: 8940 kB
Inactive: 284 kB
Active(anon): 2588 kB
Inactive(anon): 120 kB
Active(file): 6352 kB
Inactive(file): 164 kB
Unevictable: 0 kB
Mlocked: 0 kB
HighTotal: 0 kB
HighFree: 0 kB
LowTotal: 493184 kB
LowFree: 442572 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 20 kB
Writeback: 0 kB
AnonPages: 2616 kB
Mapped: 2204 kB
Shmem: 124 kB
Slab: 30528 kB
SReclaimable: 13904 kB
SUnreclaim: 16624 kB
KernelStack: 704 kB
PageTables: 296 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 246592 kB
Committed_AS: 55424 kB
VmallocTotal: 507904 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
CmaTotal: 65536 kB
CmaFree: 59280 kB
通过cat /proc/meminfo
查看系统的内存信息,其中,Slab是slab占用的内存大小,SReclaimable是可回收的,而SUnreclaim是不可回收的。发现Slab占用了系统快30M的内存,留意这个信息。接着,再查看一下,slab的详细使用情况:
root@Linux: /# cat /proc/slabinfo
slabinfo - version: 2.1
# name : tunables : slabdata
ext4_groupinfo_4k 58 81 296 27 2 : tunables 0 0 0 : slabdata 3 3 0
ext4_groupinfo_1k 1 28 288 28 2 : tunables 0 0 0 : slabdata 1 1 0
jbd2_1k 0 0 3072 10 8 : tunables 0 0 0 : slabdata 0 0 0
bridge_fdb_cache 0 0 320 25 2 : tunables 0 0 0 : slabdata 0 0 0
sd_ext_cdb 2 18 216 18 1 : tunables 0 0 0 : slabdata 1 1 0
sgpool-128 2 14 2304 14 8 : tunables 0 0 0 : slabdata 1 1 0
sgpool-64 2 25 1280 25 8 : tunables 0 0 0 : slabdata 1 1 0
sgpool-32 2 21 768 21 4 : tunables 0 0 0 : slabdata 1 1 0
sgpool-16 2 16 512 16 2 : tunables 0 0 0 : slabdata 1 1 0
sgpool-8 2 21 384 21 2 : tunables 0 0 0 : slabdata 1 1 0
cfq_io_cq 10 31 264 31 2 : tunables 0 0 0 : slabdata 1 1 0
cfq_queue 9 22 360 22 2 : tunables 0 0 0 : slabdata 1 1 0
fat_inode_cache 3 26 616 26 4 : tunables 0 0 0 : slabdata 1 1 0
fat_cache 0 0 200 20 1 : tunables 0 0 0 : slabdata 0 0 0
squashfs_inode_cache 88 200 640 25 4 : tunables 0 0 0 : slabdata 8 8 0
jbd2_transaction_s 0 42 384 21 2 : tunables 0 0 0 : slabdata 2 2 0
jbd2_inode 1 76 208 19 1 : tunables 0 0 0 : slabdata 4 4 0
jbd2_journal_handle 0 18 216 18 1 : tunables 0 0 0 : slabdata 1 1 0
jbd2_journal_head 0 68 240 17 1 : tunables 0 0 0 : slabdata 4 4 0
jbd2_revoke_table_s 4 21 192 21 1 : tunables 0 0 0 : slabdata 1 1 0
jbd2_revoke_record_s 0 16 256 16 1 : tunables 0 0 0 : slabdata 1 1 0
ext4_inode_cache 15 46 840 19 4 : tunables 0 0 0 : slabdata 4 4 0
ext4_free_data 0 72 224 18 1 : tunables 0 0 0 : slabdata 4 4 0
ext4_allocation_context 0 28 288 28 2 : tunables 0 0 0 : slabdata 1 1 0
ext4_prealloc_space 2 16 256 16 1 : tunables 0 0 0 : slabdata 1 1 0
ext4_system_zone 17 36 216 18 1 : tunables 0 0 0 : slabdata 2 2 0
ext4_io_end 0 68 232 17 1 : tunables 0 0 0 : slabdata 4 4 0
ext4_extent_status 4 72 216 18 1 : tunables 0 0 0 : slabdata 4 4 0
mbcache 0 0 224 18 1 : tunables 0 0 0 : slabdata 0 0 0
dio 0 0 640 25 4 : tunables 0 0 0 : slabdata 0 0 0
fasync_cache 0 0 208 19 1 : tunables 0 0 0 : slabdata 0 0 0
posix_timers_cache 0 0 336 24 2 : tunables 0 0 0 : slabdata 0 0 0
UNIX 20 34 960 17 4 : tunables 0 0 0 : slabdata 2 2 0
ip4-frags 0 0 304 26 2 : tunables 0 0 0 : slabdata 0 0 0
ip_mrt_cache 0 0 384 21 2 : tunables 0 0 0 : slabdata 0 0 0
UDP-Lite 0 0 896 18 4 : tunables 0 0 0 : slabdata 0 0 0
tcp_bind_bucket 1 16 256 16 1 : tunables 0 0 0 : slabdata 1 1 0
inet_peer_cache 0 0 384 21 2 : tunables 0 0 0 : slabdata 0 0 0
secpath_cache 0 0 256 16 1 : tunables 0 0 0 : slabdata 0 0 0
flow_cache 0 0 296 27 2 : tunables 0 0 0 : slabdata 0 0 0
xfrm_dst_cache 0 0 576 28 4 : tunables 0 0 0 : slabdata 0 0 0
ip_fib_trie 3 18 216 18 1 : tunables 0 0 0 : slabdata 1 1 0
ip_fib_alias 4 18 216 18 1 : tunables 0 0 0 : slabdata 1 1 0
ip_dst_cache 4 21 384 21 2 : tunables 0 0 0 : slabdata 1 1 0
PING 0 0 896 18 4 : tunables 0 0 0 : slabdata 0 0 0
RAW 8 18 896 18 4 : tunables 0 0 0 : slabdata 1 1 0
UDP 1 18 896 18 4 : tunables 0 0 0 : slabdata 1 1 0
tw_sock_TCP 0 0 328 24 2 : tunables 0 0 0 : slabdata 0 0 0
request_sock_TCP 0 0 376 21 2 : tunables 0 0 0 : slabdata 0 0 0
TCP 1 18 1728 18 8 : tunables 0 0 0 : slabdata 1 1 0
eventpoll_pwq 16 36 216 18 1 : tunables 0 0 0 : slabdata 2 2 0
eventpoll_epi 16 50 320 25 2 : tunables 0 0 0 : slabdata 2 2 0
inotify_inode_mark 0 0 232 17 1 : tunables 0 0 0 : slabdata 0 0 0
scsi_data_buffer 0 0 200 20 1 : tunables 0 0 0 : slabdata 0 0 0
request_queue 4 24 1312 24 8 : tunables 0 0 0 : slabdata 1 1 0
blkdev_requests 16 80 408 20 2 : tunables 0 0 0 : slabdata 4 4 0
blkdev_ioc 11 17 240 17 1 : tunables 0 0 0 : slabdata 1 1 0
bio-0 10 84 384 21 2 : tunables 0 0 0 : slabdata 4 4 0
biovec-max 10 45 3328 9 8 : tunables 0 0 0 : slabdata 5 5 0
biovec-128 0 0 1792 18 8 : tunables 0 0 0 : slabdata 0 0 0
biovec-64 0 16 1024 16 4 : tunables 0 0 0 : slabdata 1 1 0
biovec-16 0 18 448 18 2 : tunables 0 0 0 : slabdata 1 1 0
uid_cache 0 0 320 25 2 : tunables 0 0 0 : slabdata 0 0 0
dmaengine-unmap-2 1 16 256 16 1 : tunables 0 0 0 : slabdata 1 1 0
sock_inode_cache 42 50 640 25 4 : tunables 0 0 0 : slabdata 2 2 0
skbuff_fclone_cache 0 0 640 25 4 : tunables 0 0 0 : slabdata 0 0 0
skbuff_head_cache 0 54 448 18 2 : tunables 0 0 0 : slabdata 3 3 0
configfs_dir_cache 44 51 232 17 1 : tunables 0 0 0 : slabdata 3 3 0
file_lock_cache 0 26 312 26 2 : tunables 0 0 0 : slabdata 1 1 0
file_lock_ctx 1 19 208 19 1 : tunables 0 0 0 : slabdata 1 1 0
shmem_inode_cache 294 308 568 28 4 : tunables 0 0 0 : slabdata 11 11 0
pool_workqueue 7 21 768 21 4 : tunables 0 0 0 : slabdata 1 1 0
proc_inode_cache 376 406 552 29 4 : tunables 0 0 0 : slabdata 14 14 0
sigqueue 0 24 328 24 2 : tunables 0 0 0 : slabdata 1 1 0
bdev_cache 14 21 768 21 4 : tunables 0 0 0 : slabdata 1 1 0
kernfs_node_cache 18429 18476 264 31 2 : tunables 0 0 0 : slabdata 596 596 0
mnt_cache 25 36 448 18 2 : tunables 0 0 0 : slabdata 2 2 0
filp 344 576 448 18 2 : tunables 0 0 0 : slabdata 32 32 0
inode_cache 1461 1736 520 31 4 : tunables 0 0 0 : slabdata 56 56 0
dentry 2343 3500 320 25 2 : tunables 0 0 0 : slabdata 140 140 0
names_cache 18 35 4352 7 8 : tunables 0 0 0 : slabdata 5 5 0
buffer_head 1422 1424 248 16 1 : tunables 0 0 0 : slabdata 89 89 0
nsproxy 0 0 208 19 1 : tunables 0 0 0 : slabdata 0 0 0
vm_area_struct 1256 1320 272 30 2 : tunables 0 0 0 : slabdata 44 44 0
mm_struct 31 69 704 23 4 : tunables 0 0 0 : slabdata 3 3 0
fs_cache 33 50 320 25 2 : tunables 0 0 0 : slabdata 2 2 0
files_cache 34 48 512 16 2 : tunables 0 0 0 : slabdata 3 3 0
signal_cache 101 152 832 19 4 : tunables 0 0 0 : slabdata 8 8 0
sighand_cache 101 105 1536 21 8 : tunables 0 0 0 : slabdata 5 5 0
task_struct 106 132 1472 22 8 : tunables 0 0 0 : slabdata 6 6 0
cred_jar 134 231 384 21 2 : tunables 0 0 0 : slabdata 11 11 0
anon_vma_chain 658 702 216 18 1 : tunables 0 0 0 : slabdata 39 39 0
anon_vma 408 432 224 18 1 : tunables 0 0 0 : slabdata 24 24 0
pid 103 125 320 25 2 : tunables 0 0 0 : slabdata 5 5 0
radix_tree_node 644 656 488 16 2 : tunables 0 0 0 : slabdata 41 41 0
idr_layer_cache 192 208 1248 26 8 : tunables 0 0 0 : slabdata 8 8 0
kmalloc-8192 16 18 8448 3 8 : tunables 0 0 0 : slabdata 6 6 0
kmalloc-4096 480 483 4352 7 8 : tunables 0 0 0 : slabdata 69 69 0
kmalloc-2048 44 56 2304 14 8 : tunables 0 0 0 : slabdata 4 4 0
kmalloc-1024 201 225 1280 25 8 : tunables 0 0 0 : slabdata 9 9 0
kmalloc-512 444 462 768 21 4 : tunables 0 0 0 : slabdata 22 22 0
kmalloc-256 372 384 512 16 2 : tunables 0 0 0 : slabdata 24 24 0
kmalloc-192 277 306 448 18 2 : tunables 0 0 0 : slabdata 17 17 0
kmalloc-128 1589 1596 384 21 2 : tunables 0 0 0 : slabdata 76 76 0
kmalloc-64 15937 16200 320 25 2 : tunables 0 0 0 : slabdata 648 648 0
kmem_cache_node 107 125 320 25 2 : tunables 0 0 0 : slabdata 5 5 0
kmem_cache 107 126 384 21 2 : tunables 0 0 0 : slabdata 6 6 0
从这里可以了解到slab的使用情况,记录下来。
slab是Linux操作系统的一种内存分配机制。其工作是针对一些经常分配并释放的对象,如进程描述符等,这些对象的大小一般比较小,如果直接采用伙伴系统来进行分配和释放,不仅会造成大量的内碎片,而且处理速度也太慢。而slab分配器是基于对象进行管理的,相同类型的对象归为一类(如进程描述符就是一类),每当要申请这样一个对象,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免这些内碎片。slab分配器并不丢弃已分配的对象,而是释放并把它们保存在内存中。当以后又要请求新的对象时,就可以从内存直接获取而不用重复初始化。
接着将可以隔较长一段时间,重复的进行cat /proc/meminfo
和cat /proc/slabinfo
操作,对比几次的信息,检查问题。
最后发现,经过较长一段时间的测试之后,Slab占用的内存数量大大增加,如果是slab占用较大的内存,则是内核频繁分配结构体导致,导致系统可用内存减小。直到出现Out of memory导致kill程序。
了解到是Slab导致的占用内存过高的问题之后,可以手动的刷Slab,操作如下:
echo 3 > /proc/sys/vm/drop_caches /* 回刷缓冲 */
其中drop_caches的4个值有如下含义:
但是这样的办法不是最佳的,最好还是应该通过slabinfo信息,了解到应用程序进行什么操作,导致内核频繁申请结构体导致Slab占用大量内存,看能否避免这样的问题,同时,内核有自动回收机制,可修改触发自动回收的阀值,当slab空闲内存达到一定量的时候,进行有效的回收。
后来在参考文章看到信息,概括如下:
文中开头的说到的老化测试程序test,就是大量的保存文件,频繁的文件io操作(open、write、close),导致了dentry_cache占用了系统太多的内存资源。
inode对应于物理磁盘上的具体对象,而dentry是一个内存实体,其中的d_inode成员指向对应的inode,故可以把dentry看成是Linux文件系统中某个索引节点(inode)的链接,这个索引节点可以是文件,也可以是目录。而dentry_cache是目录项高速缓存,是Linux为了提高目录项对象的处理效率而设计的,它记录了目录项到inode的映射关系。
在slab缓存中,对象分为SReclaimable(可回收)和SUnreclaim(不可回收),而在系统中绝大多数对象都是可回收的。内核有一个参数,当系统内存使用到一定量的时候,会自动触动回收操作。
内核参数:
vm.min_free_kbytes = 836787
代表系统所保留空闲内存的最低限。
在系统初始化时会根据内存大小计算一个默认值,计算规则是:
min_free_kbytes = sqrt(lowmem_kbytes * 16) = 4 * sqrt(lowmem_kbytes)(注:lowmem_kbytes即可认为是系统内存大小)
另外,计算出来的值有最小最大限制,最小为128K,最大为64M。
可以看出,min_free_kbytes随着系统内存的增大不是线性增长,因为随着内存的增大,没有必要也线性的预留出过多的内存,能保证紧急时刻的使用量便足矣。
min_free_kbytes的主要用途是计算影响内存回收的三个参数 watermark[min/low/high]
同时,还有一个参数vm.vfs_cache_pressure = 200
该文件表示内核回收用于directory和inode cache内存的倾向;缺省值100表示内核将根据pagecache和swapcache,把directory和inode cache保持在一个合理的百分比;降低该值低于100,将导致内核倾向于保留directory和inode cache;增加该值超过100,将导致内核倾向于回收directory和inode cache。
参考文章:
Linux服务器Cache占用过多内存导致系统内存不足问题的排查解决
linux系统slab内存占用
该段更多的参考 linux系统slab内存占用 ↩︎