0 什么是內存泄漏?
內存泄漏(Memory Leak)是指程序中已動態分配的堆內存由於某種原因程序未釋放或無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重后果。
1 常見的造成內存泄漏的原因
1.1 指針重新賦值
下面是一段示例代碼:
char * p = (char *)malloc(10);
char * np = (char *)malloc(10);
其中,指針變量p和np分別被分配了10個字節的內存,它們各自的內存如圖所示
如果程序執行如下賦值語句:
p=np;
這時候,指針變量p被np指針重新賦值,其結果是p以前所指向的內存位置變成了孤立的內存,如圖所示。它無法釋放,因為沒有指向該位置的引用,從而導致 10 字節的內存泄漏。
因此,在對指針賦值前,一定確保內存位置不會變為孤立的。
1.2 錯誤的內存釋放
假設有一個指針變量 p,它指向一個10字節的內存位置。該內存位置的第三個字節又指向某個動態分配的 10 字節的內存位置,如圖所示。
如果程序執行如下語句:
free(p);
很顯然,如果通過調用 free 來釋放指針 p,則 np 指針也會因此而變得無效。np 以前所指向的內存位置也無法釋放,因為已經沒有指向該位置的指針。換句話說,np 所指向的內存位置變為孤立的,從而導致內存泄漏。
因此,每當釋放結構化的元素,而該元素又包含指向動態分配的內存位置的指針時,應首先遍歷子內存位置(如本示例中的 np),並從那里開始釋放,然后再遍歷回父節點,如下面的代碼所示:
free(p->np);
free(p);
1.3 返回值的不正確處理
有時候,某些函數會返回對動態分配的內存的引用,如下面的示例代碼所示:
char *f()
{
return (char *)malloc(10);
}
void f1()
{
f();
}
很明顯,函數 f1 中對 f 函數的調用並未處理該內存位置的返回地址,其結果將導致 f 函數所分配的 10 個字節的塊丟失,並導致內存泄漏。
1.4 內存malloc()分配后忘記使用free()進行釋放
1.5 使用open()、fopen()后忘記使用close()、fclose()
2 內存泄漏的表現
2.1 應用程序崩潰
因為內存泄漏導致已運行的應用程序得不到所需的內存空間而出現崩潰。
2.2 內存占用量持續增長不減
內存泄漏會導致被泄露的內存在本次系統運行期間不能被使用,因此,隨着時間的推移,內存占用量是持續增長的。
2.3 觸發OOM,進程被kill
當系統無法為新進程分配內存時,可能會觸發OOM,系統選擇一些進程,然后kill他們。
3 如何定位內存泄漏
3.1 用戶態內存泄漏
- 使用top指令查看系統中占用內存量較高的進程
top
觀察以下參數:
VIRT 進程使用的虛擬內存
RES 進程使用的真實內存
SHR 共享內存
%MEM 內存占用率
M 按內存占用率(%MEM)排序
2. 找出內存占用量較高的進程后,記下該進程的PID,如上圖的609。
3. 執行如下指令查看進程內存方面的詳細信息。
watch -n 1 cat "/proc/"`ps -ef | grep PID | grep -v grep | awk 'NR==1 {print $2}'`"/status"
我們主要查看一下幾個參數的值:
VmPeak:進程使用的虛擬內存的峰值
VmSize:進程當前使用的虛擬內存的大小
VmLck:已鎖住的物理內存的大小(不能交換到磁盤)
VmHWM:進程使用的物理內存的峰值
VmRSS:進程當前使用的物理內存的大小
如果VmSize或VmRSS在一段時間內占用異常,可以考慮該進程是否存在內存泄漏。
4. 借助內存泄漏分析工具進行分析
推薦使用內存泄漏分析工具--valgrind
valgrind --tool=memcheck --leak-check=full xxx //xxx為該進程的二進制可執行文件的絕對路徑
執行完畢后我們要觀察的主要是如下圖的信息:
Definitely lost:確認丟失,程序中存在內存泄漏,需要盡快修復。
Indirectly lost:間接丟失,常與definitely lost一起出現,只要修復definitely lost即可。
Possibly lost:可能丟失,大多數情況下應視為definitely lost,需要盡快修復。
Still reachable:可以訪問的,這些內存沒有丟失、沒有釋放。建議修復。
Suppressed:已解決的,可以無視此部分。
5. 假如有內存泄漏,則需要找出源碼進行修復。
那如何快速定位是哪個位置出現了內存泄漏呢?
根據valgrind的打印信息快速定位。舉例如下:
這個例子就說明了/root目錄下的a.out程序存在4字節的內存泄漏,即test()函數里面的new操作存在泄漏,即new完沒有對應的delete。(從下往上執行)
找到泄漏的原因,我們就可以在源碼中找到對應的位置進行修復。
6. valgrind也不是萬能的,存在如下缺點(包括但不限於):
- valgrind會占用了更多的內存--會達到檢測程序的兩倍
- valgrind不檢查靜態分配數組的使用情況
- valgrind檢測時新啟動了一個進程,不能監測正在運行的進程
3.2 內核態內存泄漏
對於內核態內存泄漏的排查方法和工具,我不了解。如有好的參考資料可以評論區留言,謝謝各位dalao。
本文持續更新