前言
問題真是一個接一個勘天,做開發(fā)就是解決一個又一個問題嗎滩报?
像死機宝惰、內(nèi)存泄漏這些問題很多時候是沒有框架足丢、設計或有了框架和設計但是團隊沒有統(tǒng)一遵循標準按著自己性子來導致的粱腻,統(tǒng)一的框架和設計也許會損失一定的靈活性庇配,但是他會讓你在編碼的時候遵從一定的范式,且通過規(guī)范格式可以做到良好的自檢查绍些,例如將一個代碼的實現(xiàn)分別放在A捞慌、B、C三個地方柬批,A啸澡、B、C氮帐、分別干啥嗅虏,接口是啥,A揪漩、B旋恼、C的范例等都在框架和設計中定義好了吏口,每個編碼人員按這個進行具體的開發(fā)編碼工作奄容,可以并行有序,甚至A产徊、B昂勒、C進行不同的分工開發(fā)互不影響,比如A是解析配置的舟铜,B是做初始化的戈盈,C是業(yè)務處理主體。
對于應用開發(fā)谆刨,從框架塘娶、設計及編碼質(zhì)量這些工程角度避免低級錯誤,把開發(fā)的大部分時間用在算法痊夭、業(yè)務邏輯實現(xiàn)與優(yōu)化等給客戶帶來良好體驗與價值的地方刁岸,是一個軟件團隊需要努力的方向。
好吧她我,回到內(nèi)存泄漏虹曙,這里指的是那些默默消耗了系統(tǒng)有限內(nèi)存的操作,不僅僅指代碼中申請而沒有釋放的內(nèi)存番舆,簡單總結了下面幾種情況進行檢查和定位:
1酝碳,應用進程造成的內(nèi)存泄漏
2,內(nèi)核空間/或因應用進程的操作導致內(nèi)核空間的內(nèi)存泄漏
3恨狈,用戶空間產(chǎn)生的臨時文件(如日志等)造成的內(nèi)存泄漏
應用進程造成的內(nèi)存泄漏
1)采用ps或top命令進行觀察
首先疏哗,我們習慣性的會看下那個進程在泄漏內(nèi)存,我這里使用一個test_core_dump的測試進程禾怠,該進程每2秒鐘會分配一個10000字節(jié)的空間沃斤,并作簡單賦值(注意:如果僅malloc而不使用圣蝎,編譯器會優(yōu)化,實際測試時將看不到內(nèi)存泄漏的測試效果):
root@OpenWrt:/# ps
? PID USER? ? ? VSZ STAT COMMAND? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
14444 root? ? 15984 S? ? test_core_dump
省略不必要的信息衡瓶,這里徘公,我們主要看VSZ這個字段,這個字段表示該進程的虛擬內(nèi)存大小哮针,單位為KBytes关面,也就是此時我的測試進程已經(jīng)占用近16M的虛擬內(nèi)存空間,那是否意味著物理內(nèi)存就已經(jīng)被吃掉16M呢十厢,這個我等下再講等太。
或者我們通過top命令,也可以觀察進程的VSZ字段蛮放,這里我當時沒抓速據(jù)缩抡,所以就不放速據(jù)了,不管是top還是ps包颁,重點是看VSZ字段的變化瞻想,當你發(fā)現(xiàn)VSZ字段一直在變化,則很明顯這個進程有內(nèi)存泄漏的問題娩嚼,比如我現(xiàn)使用ps再看:
root@OpenWrt:/# ps?
? PID USER? ? ? VSZ STAT COMMAND? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
14444 root 73884 S test_core_dump
我的測試進程已經(jīng)占用近73M的虛擬地址空間蘑险。
2)查看/proc/meminfo
一般我們在發(fā)現(xiàn)有進程內(nèi)存泄漏時,還會去查看一個文件岳悟,就是/proc/meminfo佃迄,看系統(tǒng)還有多少內(nèi)存,比如我在上一步第一次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
看了系統(tǒng)還有MemFree:? ? ? ? ? 28536 kB贵少,約28M內(nèi)存(我在系統(tǒng)啟動完成時呵俏,就看了一次,大概是剩39M內(nèi)存)滔灶,那是不是這11M內(nèi)存都是我的測試程序吃了呢普碎?從第一次ps看到的虛擬內(nèi)存16M來看,好像數(shù)據(jù)對不上宽气?
不妨在第二次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看到測試進程占用的虛擬內(nèi)存為73M萄涯,而此時MemFree:? ? ? ? ? 21936 kB绪氛,約為21M,也就是物理內(nèi)存距上次的28M減少到21M涝影,吃掉了7M枣察,但虛擬內(nèi)存是16M增長到73M,這明顯不是一個換算關系,接下來我們看物理內(nèi)存在哪里看:
3)查看物理內(nèi)存占用序目,通過/proc/pid/stat或/proc/pid/statm(這里不能設置字體顏色臂痕,以粗體表示)
root@OpenWrt:/# cat /proc/14444/statm
21861 2332 202 1 0 20985 0
我們看到的21861表示VSZ,2332表示物理內(nèi)存猿涨,這里的單位是頁(page)握童,我所用的系統(tǒng)當前頁單位設置是4KB/頁,所以可以得出:
VSZ =?21861 *4 = 87444KB叛赚,約87M(使用3)查看時澡绩,已距73M過了一段時間)
物理內(nèi)存=2332?*4 = 9328,約9M俺附,這時我們可以看到肥卡,基本可以和/proc/meminfo對的上了
或者,我們采用下面命令進行查看事镣,同樣可以看到該結果步鉴,這是下面文件參數(shù)太多,如果只是看內(nèi)存璃哟,建議使用上個文件(注:下個文件中的89702400字段單位為Bytes而不是頁氛琢,有些網(wǎng)上的文檔解釋成頁,就誤導了沮稚,而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
此時艺沼,通過上面的命令及文件的分析册舞,基本可以定位或查找出應用進程的內(nèi)存泄漏問題蕴掏。
注:
1)ps和top命令很多網(wǎng)上的文檔會說有VIRT、RES和SHR字段调鲸,但這可能是在PC端的linux盛杰,在嵌入式中,我遇到的僅有VSZ字段藐石,相當于VIRT即供,所以要通過/proc/pid下的文件輔助
2)參考1):/proc/pid/stat字段說明(該文章用紅色字體標明vsize的錯誤,贊)
3)參考2):進程的虛擬內(nèi)存于微,物理內(nèi)存逗嫡,共享內(nèi)存(包括statm文件格式說明),這里解釋了虛擬內(nèi)存和駐留內(nèi)存(我理解實際使用的內(nèi)存)的關系株依,就是上面VSZ和/proc/meminfo的數(shù)據(jù)關系
4)參考3):proc/meminfo 文件內(nèi)存詳解
內(nèi)核/或因應用進程的操作導致內(nèi)核空間的內(nèi)存泄漏
排查內(nèi)核空間的內(nèi)存泄漏驱证,這里主要爭對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內(nèi)存,通過查看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內(nèi)存的使用(57-42)出爹,而我們再仔細看,這其中SReclaimable約前后相差12M(23-11)缎除,這部分空間是可以回收的严就。既然這塊內(nèi)存變化較大,那我們重點看下slab的內(nèi)存分配情況:
2)cat /proc/slabinfo器罐, 這里信息太多梢为,不全部放上來,我采集了兩個速據(jù)做對比轰坊,這兩個速據(jù)之間有一個動作铸董,第3)步再講:
有一個比較好的命令可以顯示的比cat /proc/slabinfo更直觀,但是我之前沒有去了解肴沫,因為習慣性動作粟害,直接cat文件,雖然一直覺得這個文件不是很直觀颤芬,這個第4)步講一下悲幅。我們來看這個數(shù)據(jù),按照常規(guī)的邏輯站蝠,先找最大的那個數(shù)汰具,或者我們慣性覺得最大的數(shù)就是異常的,在圖1中我們看到dentry比較異常菱魔,共計153381個num_objs對象留荔,每個對象136字節(jié),占用空間約19M澜倦,我們看圖2第二個異常kmalloc-128聚蝶,共計124200個num_objs對象,每個對象128字節(jié)藻治,占空間約15M碘勉。
通過分析內(nèi)存占用較大的slab可以定位大概是哪里的問題,比如這兩個占用內(nèi)存比較大的slab栋艳,第一個dentry和文件操作相關恰聘,應該是應用層頻繁的文件操作導致內(nèi)核頻繁申請dentry內(nèi)存所致;第二個kmalloc-128是內(nèi)核模塊自己申請內(nèi)存導致,這個可以首先排查自己私有模塊用到128字節(jié)以內(nèi)分配的調(diào)用晴叨,然后排查內(nèi)核本身(如果這里有問題凿宾,內(nèi)核本身的問題概率較少)。
3)嘗試釋放Slab占用的cache內(nèi)存空間
雖然我們看到有兩個slab占用較多兼蕊,但是不能說這就是內(nèi)存泄漏(或者也可以說是一種泄漏吧初厚,只是不是因為kmalloc后沒有kfree),也許是他就是要用到這么多內(nèi)存孙技,只是因為某種設計原因導致他過多和過快的占用了內(nèi)存产禾,影響到系統(tǒng)的穩(wěn)定性。因為我們前面也看到牵啦,SReclaimable:? ? ? 23040 kB有23M之多亚情,這些是可回收和利用的,只是我們的系統(tǒng)還沒有觸發(fā)回收機制哈雏。
當前做一個簡單的嘗試楞件,使用命令:
echo?2?>?/proc/sys/vm/drop_caches
該命令釋放dentries and inodes的可回收slab內(nèi)存。
我們再來看裳瘪,釋放后的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內(nèi)存,基本吻合派殷,以此也可進一步判斷這些內(nèi)存不是因為沒有kfree進了黑洞还最,而是因為kfree后還沒有被系統(tǒng)回收和利用。那么我看在看看slabinfo愈腾,看數(shù)據(jù)是否也吻合憋活,我們可以參考圖1與圖2的對比:drop_caches后岂津,dentry虱黄,共計7714個num_objs對象,每個對象136字節(jié)吮成,占用空間約1M橱乱;kmalloc-128,共計97350個num_objs對象粱甫,每個對象128字節(jié)泳叠,占空間約11M。對比drop_caches前茶宵,相當于(19+15-1-11)=22M危纫,和回收了20多M內(nèi)存也基本吻合。但從這里可以得出經(jīng)驗:
1,kmalloc-128還是有嫌疑种蝶,這個還是可以通過排除私有內(nèi)核模塊來處理契耿,比如在系統(tǒng)啟動后,所有模塊加載完成的第一時間看slabinfo螃征,如果在11M左右搪桂,說明確實需要,沒有泄漏的異常盯滚,如果這個數(shù)據(jù)越來越大踢械,通過drop_caches也回收不了且回收后大于11M較多,那就要檢查內(nèi)核相關模塊魄藕;
2内列,dentry內(nèi)存如果是因為應用層導致,則基本是可回收的背率,因為不是內(nèi)核本身的異常德绿,所以這時需要優(yōu)化應用層相關的程序避免這個問題。
4)slabtop命令
Slab是用于存放內(nèi)核數(shù)據(jù)結構緩存退渗,再通過slabtop命令查看這部分內(nèi)存的使用情況移稳,這個和查看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內(nèi)存管理文章:詳細講解從用戶空間申請內(nèi)存到內(nèi)核如何為其分配內(nèi)存的過程,我把上面講的內(nèi)容放在一起翻翩,大概就是一個這樣的圖:
2)參考:Linux Malloc分析-從用戶空間到內(nèi)核空間
3)drop_caches相關參考都许,及設置內(nèi)存回收的條件和時機:Linux服務器Cache占用過多內(nèi)存導致系統(tǒng)內(nèi)存不足問題的排查解決,Linux服務器Cache占用過多內(nèi)存導致系統(tǒng)內(nèi)存不足問題的排查解決(續(xù))
用戶空間產(chǎn)生的臨時文件(如日志等)造成的內(nèi)存泄漏
這個其實開發(fā)人員自己心里可能是清楚的嫂冻,因為這些臨時文件基本是自己產(chǎn)生的胶征,但因為缺乏統(tǒng)一設計(比如統(tǒng)一使用syslog)和沒上心,可能把這個問題帶到測試或客戶環(huán)境桨仿,可以通過命令du -sh查看: