1. 程式人生 > 其它 >java 記憶體洩漏排查_如何排查記憶體洩漏

java 記憶體洩漏排查_如何排查記憶體洩漏

技術標籤:java 記憶體洩漏排查valgrind 記憶體洩漏一個解決方案多個專案使用vld檢測記憶體洩漏

知其然並知其所以然,從基礎開始,我們深入觀察各種酷炫技術的內在實現


5d34eb3d3c1d629328957a4a2c8bb0eb.gif


在我們寫一個龐大專案的時候,比如我們為公司寫一個面向具體業務的引擎核心的時候,我們並沒有什麼卵詳盡的開發計劃和設計,往往我們都是一邊設計,一邊想實現,需求還經常變化,這是無法避免的,就像是你去打副本,你只知道要把人家Boss收拾了才能拿到裝備,至於你每次去的時候組到了什麼隊友,怎麼收拾的Boss,那可能就千變萬化了。 從設計者的角度來看 這種開發流程是非常棒的3dd4f3fa8861bd5472b0b279c4a89b54.png 因為非常的自由與靈活 碰到問題隨機應變、靈活處置 但是從施工隊的角度來說 這必然會導致專案變得一團糟 胸中除了羊駝在奔騰以外 殺人的心都有了c824b706a0f979342bd037175aed5bbb.png
當專案的dead line臨近的時候,我們忽然開始意識到,我們需要把程式碼整理一下,把沒用的程式碼刪掉,讓專案變得穩定起來,要開始準備Alpha測試了。 如果你是在一個小公司或者大公司裡的一個小團隊的話,至少在一般的遊戲公司中,依然是大公司下圈養著一群互相獨立的小作坊的模式。如此一來,你就不可能指望能拿到一大堆專業的測試工具來幫助你把專案測試一遍。於是我們就需要一個解決方案,這個解決方案能夠很輕鬆的植入到現有的專案程式碼中,同樣,我們希望這樣的一個解決方案是通用的,而不是說我們需要為每一個專案都搞一套記憶體洩漏檢查方案。我們希望這樣的一套記憶體檢測機制能夠非常容易的在下一次專案中也能用上。在程式執行結束的時候,我能夠通過這個解決方案拿到程式未釋放的記憶體的一個清單,這樣就可以幫助我去排查記憶體的洩漏了。 已經報名了我們引擎課程的同學應該是想到了,我們講過的記憶體池的實現,其實那個不僅僅是實現了記憶體池來加速記憶體的分配和回收速度,更大的作用在於幫助你去非常方便的去Debug你專案的記憶體的使用狀況。61645a9c8bd876344cb6f7be64ee440b.png
系統工程-複雜系統的記憶體管理層 現在讓我來更詳盡的闡明,我們的記憶體追蹤器到底需要什麼。首先,我們需要這個記憶體追蹤器能夠新增到現有的專案中,且不要對現有的專案進行大的改動。程式碼重用是非常重要的,尤其是你在一個公司裡面幹活的時候。它可能節省很多時間與金錢。其次,我們的解決方案必須足夠簡單。我們不能夠為了實現對專案中所有記憶體使用的追蹤而對現有的程式碼大改特改。最後這個解決方案必須是免費的c6f6c3ec15fd2bdecf1933fc1de70405.png。 雖然大家都說免費的東西是最貴的 所以我們現在來看一眼我們專案裡的程式碼,我們發現的第一個東西就是,所有的記憶體分配的操作都是通過new操作符以及它的變種來完成的。同樣的,所有的記憶體回收函式都是通過delete操作符以及它的變種來完成的。 那麼我們是不是隻需要使用什麼專門的函式來替換掉現在的這些new和delete操作符就可以完成追蹤記憶體的目的了呢? 不是的!這麼幹,你要改動的程式碼太多了。C++允許你去為你的類過載new和delete操作符。如果這不是意味著你需要為你的所有的類去過載new和delete運算子的話,這將是一個非常棒的解決方案。等等...我們也可以過載全域性的new和delete操作的啊!!!過載了全域性的new和delete操作符了之後,你就可以在每一次分配記憶體和釋放記憶體的時候幹任何你想幹的事情了。這真的是太棒了!其實我們的引擎同學應該已經學完這塊了,畢竟這是整個引擎的根基,我們教會同學們的豈止是一個過載new和delete的操作符,還有我們的C++Tricks中講述的,我們是在教會同學們整套底層的核心級玩法。 言歸正傳,現在我們只需要在記憶體塊被邏輯層需要的時候標記一下,如果程式執行結束的時候,這塊記憶體沒被還回來,那麼它就是洩漏了! 由於我們這裡不是在實現記憶體池管理解決方案,所以我們這裡加了_DEBUG巨集,引擎課程學習的同學可以不必管這裡講的這些,跟著引擎課的內容走就可以了,那邊是整套成體系的內容。
#ifdef_DEBUGinlinevoid*__cdecloperatornew(unsignedintsize,constchar*file,intline){};inlinevoid__cdecloperatordelete(void*p){};#endif

上面就是現階段的全部程式碼了,我們就過載了這倆函式,並且我們用#ifdef和#endif把它們框起來了,因為我們說了,我們本文介紹的是記憶體追蹤,不是記憶體池演算法。如果你實現了記憶體池演算法,那麼你的這個過載應該是一直開著的。記憶體池的好處咱們就不詳細的說了,總而言之,就是讓你的系統更加的穩定和可Debug。

由於我們還希望知道我們記憶體分配的時候,是哪個檔案的哪行程式碼分配的記憶體洩漏了,所以我們的使用了下面的巨集來進一步的封裝了new運算子,這樣一來,你就無需要改變你現有的程式碼,它們會自動的給自己新增上自己是在哪個地方初始化的記憶體塊。

#ifdef_DEBUG#define DEBUG_NEW new(__FILE__, __LINE__)#else#define DEBUG_NEW new#endif#define new DEBUG_NEW

現在我們來實現我們的new和delete函式,我們在初始化記憶體塊後,對它進行追蹤,在釋放記憶體塊之後,取消對它的追蹤,如此一來,程式執行結束之後,還處於追蹤狀態的記憶體塊就是沒被釋放的記憶體塊,也就是記憶體洩漏了。

#ifdef _DEBUGinlinevoid*__cdecloperatornew(unsignedintsize,constchar*file,intline){    void*ptr=(void*)malloc(size);    AddTrack((DWORD)ptr, size, file, line);    return(ptr);};inlinevoid__cdecloperatordelete(void*p){    RemoveTrack((DWORD)p);    free(p);};#endif

下面的程式碼展示的就是追蹤記憶體塊和取消對記憶體塊追蹤的程式碼,我們會把所有的記憶體塊都串成一個連結串列。

typedef struct {  DWORD  address;  DWORD  size;  char  file[64];  DWORD  line;} ALLOC_INFO;typedef list AllocList;AllocList *allocList;void AddTrack(DWORD addr,  DWORD asize,  const char *fname, DWORD lnum){  ALLOC_INFO *info;  if(!allocList) {    allocList = new(AllocList);  }  info = new(ALLOC_INFO);  info->address = addr;  strncpy(info->file, fname, 63);  info->line = lnum;  info->size = asize;  allocList->insert(allocList->begin(), info);};void RemoveTrack(DWORD addr){  AllocList::iterator i;  if(!allocList)    return;  for(i = allocList->begin(); i != allocList->end(); i++){    if((*i)->address == addr){      allocList->remove((*i));      break;    }  }};

最後這段程式碼就是在程式執行結束的時候去打印出來還沒有被釋放的記憶體塊的程式碼,這些記憶體塊就是記憶體洩漏的地方。由於我們之前記錄了每一塊記憶體是從哪裡分配出去的,所以我們很容易就知道,我們的程式碼是哪裡出現了記憶體洩漏。

voidDumpUnfreed(){  AllocList::iterator i;  DWORD totalSize = 0;charbuf[1024];  if(!allocList)return;  for(i = allocList->begin(); i != allocList->end(); i++) {    sprintf(buf, "%-50s:\t\tLINE %d,\t\tADDRESS %d\t%d unfreed\n",      (*i)->file, (*i)->line, (*i)->address, (*i)->size);    OutputDebugString(buf);    totalSize += (*i)->size;  }  sprintf(buf, "-----------------------------------------------------------\n");  OutputDebugString(buf);  sprintf(buf, "Total Unfreed: %d bytes\n", totalSize);  OutputDebugString(buf);};

故事的最後

我們為同學們介紹一個程式分析工具

這個工具的名字叫:valgrind

它的功能是自動檢測記憶體管理

以及執行緒BUG

官網地址如下:

http://www.valgrind.org/