描述
最近一段時間,伺服器頻繁出現記憶體增長嚴重,導緻伺服器性能極具下降,由于伺服器代碼比較龐大,而且是線上問題,是以處理起來比較棘手,好在我通過一些手段定位到了bug的具體位置,故以記錄之。
由于是線上問題,是以像valgrind、gdb、memstack基本不适用,就算你是gdb高手,通過gdb adb attach pid 和gdb dump memory 也是不能很準确定位,而valgrind本身的消耗會影響别人測試或者調試。
c/c++程式的記憶體洩漏,個人認為有幾種情況:
1、malloc、memalagin、realloc、calloc沒有free
2、給記憶體指針重新指派
3、越界導緻的記憶體指針被修改,這個更難,還得解決越界的問題。
4、函數傳回的是動态記憶體指針,卻被忽視,如strdup函數
5、結構體本身是動态記憶體指針,結構體成員裡邊有動态指針,隻釋放了結構體,沒有釋放成員。
6、第三方動态庫存在記憶體洩漏,這個比較麻煩,因為第三方庫沒有調試資訊。但一般pmap能看一部分資訊。
辦法
我本人定位問題,其實也是比較笨的辦法,基本上是克隆一台機器,設定相同的觸發條件,然後在背景調試輸出,也用gdb和valgrind,效率不高。我們的線上問題是發送端速度快,接收端速度慢,導緻中間代理層記憶體暴增。
一、mtrace
mtrace()函數為記憶體配置設定函數安裝鈎子函數(malloc、realloc、memalign、free)。 這些挂鈎函數記錄有關記憶體配置設定的跟蹤資訊和重新配置設定。 跟蹤資訊可用于發現記憶體洩漏并嘗試釋放程式中未配置設定的記憶體。
程式設計接口:
#include <mcheck.h>
void mtrace(void);
void muntrace(void);
代碼實作:
#include <mcheck.h>
int main(int argc, char **argv)
{
setenv("MALLOC_TRACE","output",1);
mtrace();
}
運作程式之後,在程式的目前目錄下會生成output檔案,然後使用指令擷取堆棧資訊:
# mtrace 程式名 output檔案 >msg.txt
通過檢視msg.txt檔案,就可以找到記憶體洩漏的地方、大小,如:
Memory not freed:
-----------------
Address Size Caller
0x0000000001ed4760 0x18 at 0x7fface2c1780
0x0000000001ed47b0 0x18 at 0x7fface2c1780
0x0000000001ed59f0 0xb0 at 0x7fface2c1780
0x0000000001ed5ab0 0x18 at 0x7fface2c1780
0x0000000001ed5ad0 0x18 at 0x7fface2c1780
0x0000000001ed5af0 0x18 at 0x7fface2c1780
2、封裝malloc,輸出調試資訊
封裝代碼:
void *my_malloc(int size, int zero,const char *file,int line,const char*func)
{
char *rv;
rv = (char *)malloc(size);
if (zero)
{
if (rv != 0)
{
memset(rv, 0, size);
}
}
printf("malloc %s:%d:%s:(%d):%p\n",file,line,func,size,rv);
return rv;
}
void my_free(void *ptr,const char *file,int line,const char*func)
{
if (ptr != 0)
{
printf("free %s:%d:%s:%p\n",file,line,func,ptr);
free(ptr);
}
}
頭檔案接口代碼
void *my_malloc(int size, int zero,const char *file,int line,const char*func);
void my_free(void *ptr,const char *file,int line,const char*func);
#define mem_free(ptr) my_free(ptr, __FILE__,__LINE__,__func__)
#define mem_malloc(size, zero) my_malloc((size), zero,__FILE__,__LINE__,__func__)
然後在測試的時候會出現很多調用mem_malloc和mem_free的輸出資訊,可以重定向到一個日志檔案,在程式結束時分析日志檔案,主要找malloc的指針有沒有對應的free輸出。可以針對性的寫程式進行分析,雖然分析的過程比較慢,但如果分析出來,就能直接根據檔案、函數名、行号定位到具體位置。
3、在malloc和free的函數中寫記錄
在malloc中把指針值寫到緩存中,free中把指針值delete掉。在寫指針的時候,需要把檔案、行号,函數名寫進去。這樣在程式結束的時候,把緩存資料寫到檔案中,通過檢視檔案一目了然。
建議
我個人認為在高性能伺服器的開發中,随程式運作動态maloc和free是不可取的,而是要使用記憶體池。像nginx,redis這種高性能中間件,基本都是使用的記憶體池。記憶體池能夠很好的避免記憶體無限制增長,在排查問題的時候也比較好定位。
其實以上寫的内容在c++中,構造函數和析構函數也能用。如果是c語言,我建議還是在單元測試的時候使用valgrind和gdb調試。
好的c語言程式,一定是合理利用記憶體,尤其在并發網絡伺服器中,每個連接配接的記憶體資源配置設定決定了整體性能,像多線程比多程序的記憶體使用率高,但出問題會導緻整個服務崩潰,那麼既要多程序的穩定性,又要多線程的性能,那麼使用協程就是好的解決辦法。