當(dāng)進程通過malloc申請虛擬內(nèi)存后,操作系統(tǒng)不會立即為其分配物理內(nèi)存,而是在首次訪問時,才觸發(fā)缺頁異常分配內(nèi)存。對普通進程來說,能看到的是內(nèi)核提供的虛擬內(nèi)存,這些虛擬內(nèi)存還需要通過頁表,由系統(tǒng)映射為物理內(nèi)存。
為平衡CPU與磁盤間的性能差異,Linux會使用Cache把文件數(shù)據(jù)緩存到內(nèi)存中。
內(nèi)存分配
用戶空間內(nèi)存包括多個不同類型的內(nèi)存段,比如只讀段、數(shù)據(jù)段、堆、棧以及文件映射段等,下面逐個分析哪些地方可能出現(xiàn)內(nèi)存泄漏。
只讀段,包括程序的代碼和常量,只讀段不會再去分配新的內(nèi)存,因此不會產(chǎn)生內(nèi)存泄漏。
數(shù)據(jù)段,包括全局變量和靜態(tài)變量,這些變量在定義時就已經(jīng)確定了大小,也不會產(chǎn)生內(nèi)存泄漏。
棧內(nèi)存由系統(tǒng)自動分配和管理,只會出現(xiàn)爆?;蛘卟葍?nèi)存情況,超出了局部變量的作用域時,棧內(nèi)存會被系統(tǒng)自動回收,不會出現(xiàn)內(nèi)存泄漏情況。
堆內(nèi)存由應(yīng)用程序自己來分配和管理,如果業(yè)務(wù)沒有正確釋放堆內(nèi)存,就會造成內(nèi)存泄漏,所以沒有特殊情況,盡量使用智能指針管理對象生命周期。
內(nèi)存映射段,包括動態(tài)鏈接庫和共享內(nèi)存,其中共享內(nèi)存由程序動態(tài)分配和管理。所以如果程序在分配后忘了回收,就會導(dǎo)致內(nèi)存泄漏。
對于內(nèi)存泄漏,只能依賴系統(tǒng)OOM機制殺死進程,但是此時已經(jīng)造成了嚴重的性能問題,如系統(tǒng)的緩存頻繁回收等,會導(dǎo)致業(yè)務(wù)不可用。
系統(tǒng)命令
top?能觀察系統(tǒng)和進程的內(nèi)存占用情況,vmstat能觀察內(nèi)存的變化趨勢。
觀察free列,如果一直在減小,說明系統(tǒng)可用內(nèi)存在變少??梢酝ㄟ^top或ps來觀察進程的內(nèi)存使用情況,然后找出內(nèi)存使用一直增長的進程,最后再通過pmap分析進程地址空間中內(nèi)存的使用情況。
使用緩沖區(qū)分析工具cachetop分析這些緩存被哪些進程占用;free查看可用內(nèi)存被哪些進程占用。
cachetop可以分析業(yè)務(wù)是否有繞過緩存直接讀取磁盤,導(dǎo)致讀寫速度慢;strace可以查看具體的系統(tǒng)調(diào)用:
strace -p $(pgrep)?PID
檢測工具
如果是編碼階段,在上線前檢查代碼中資源獲取的方式,盡量改為智能指針自動管理資源生命周期;禁止系統(tǒng)的swap機制,減少內(nèi)存和硬盤的交換數(shù)據(jù)導(dǎo)致性能開銷。
如果是調(diào)試階段檢查內(nèi)存泄漏,可以mock掉業(yè)務(wù)的malloc和free,在malloc/new時將調(diào)用方法、申請地址寫入指定文件新增一條記錄,free時根據(jù)地址刪除該條記錄,在程序運行結(jié)束后可以從文件中找到內(nèi)存泄漏的地方。
bcc中的memleak就是類似思路,利用插樁統(tǒng)計分配的字節(jié)和釋放的字節(jié),兩者進行抵消,而沒有釋放的調(diào)用棧就是可能泄露的地方。
其他內(nèi)存泄漏檢測工具:
AddressSanitizer:速度快,首選;
Valgrind:攔截內(nèi)存分配和釋放函數(shù),無需修改代碼,但是速度比較慢;
mtrace:通過環(huán)境變量替換malloc和free函數(shù),方便使用。