內存泄露是一個比較基礎,也比較古老的話題,它是指程序中的無用內存持續堆積,但是又沒有得到及時釋放,從而導致程序內存佔用過高,拖慢了運行速度,如果問題嚴重的話,程序還可能直接卡死。

當然,其它程序以及操作系統也會跟着遭殃,都會因為計算機內存資源的耗盡而變成蝸牛,一動不動。

解決內存泄露的辦法也非常簡單,就是退出程序,然後重啓!

不過對於服務器上的程序,或者計算機的底層組件,頻繁地重啓是無法接受的,那將導致工作暫停,或者服務中斷。所以,大量的內存泄露是一個非常嚴重的 Bug,我們必須要足夠重視,並且着手解決.

在瞭解內存泄露之前,我們先來看一下程序的內存佈局。下圖是 32 位 Linux 系統下一個進程的內存分段示意圖。

大白話聊內存泄漏(一看就懂)_內存泄露

在 32 位環境下,一個程序佔用 4GB 的內存,其中「內核空間」是被操作系統佔用的,我們沒法直接干預;「保留區域」也不用來存儲數據,只用作一些特殊目的,比如,你可以讓空指針指向這裏。

除了這兩個區域,剩下的那些內存才是被我們自己編寫的程序所佔用的,也就是圖中的①和②兩個部分,這其中:

  • ② 在程序運行期間會一直存在,直到程序銷燬才能被釋放;
  • ① 在程序運行期間會不斷變化,它被不斷地分配和釋放。

在第①部分中,有一個分段叫做堆(Heap),這才是我們能夠隨意操作的內存,你可以隨時分配,也可以隨時釋放,屬於真正的自由空間。

操作系統不會干預堆區內存,我們只能自己手動管理,如果我們分配了一段內存,後來它用完了,但是我們卻沒有及時釋放,這個時候就會發生內存泄露。

被泄露的內存,在程序運行期間會一直存在,並且會持續積累,直到程序運行結束後才會被操作系統一次性回收。

除了堆區,其它區域的內存都由操作系統管理,要麼用完即毀,要麼一直存在,從來不會發生內存泄露。

還記得C語言中的 malloc() 和 free() 函數吧,它們就是在堆上分配和釋放內存。

下面讓我們看一段內存泄露的代碼:

#include 
 #include 
 int main(){
     int *pOld = (int*) malloc( sizeof(int) );  //內存塊1
     int *pNew = (int*) malloc( sizeof(int) );  //內存塊2
     pNew = pOld;
 
     //free(pOld)和free(pNew)只能調用一個
     free(pOld);
     //free(pNew);
 
     return 0;
 }

剛開始 pOld 和 pNew 指向兩塊不同的內存,但是把 pOld 賦值給 pNew 以後,它們就都指向內存塊1了。

這個時候你會發現,內存塊2沒有指針指向它了!

這意味着,我們永遠無法知道內存塊2的地址了,它就像消失在堆區的海洋裏一樣,再也找不到了,再也沒法釋放了。然而,它又實實在在地存在於堆區之中,不能被其它數據佔用。

這種內存丟失,就是一種典型的內存泄露。

上面的代碼比較簡單,有經驗的程序員可以輕鬆應對,但是在實際開發中,函數之間的調用關係非常複雜,有時候你搞不清應該在什麼地方釋放內存,所以你乾脆就不釋放了。

雖然這塊內存沒有丟失,但是你沒有及時釋放,它也會成為垃圾內存,隨着程序的運行而持續堆積。這也是內存泄露的一種常見情況。

搞清楚了內存泄露的概念,你會發現,內存泄露並不是一種錯誤,只是一種由於管理不善而導致的潛在隱患。

如果內存泄露的不多,比如只有 10KB,或者 30MB,那麼它就不會影響程序運行,問題就不算嚴重,甚至你都發現不了。有一些比較寬鬆的公司,可能壓根都不把它當做一個問題。

只有當內存泄漏的足夠多了,比如 1GB,或者3GB,這個時候才會影響程序運行,我們才必須要去解決。

內存泄露是 C/C++ 中最難發現和解決的 Bug,即使非常有經驗的程序員,也不能保證自己的程序沒有內存泄露。

幫助解決內存泄露的辦法有很多,比如 Valgrind、Windbg、Address Sanitizer(ASan)內存分析工具,以及 mtrace()、ccmalloc() 函數等,但是它們也只能解決一些常見的問題,對於那些複雜或者罕見的問題往往也力不從心。

另外,它們還比較考驗程序員的功力,並不是傻瓜式操作。

總之,內存泄露是一個困擾 C/C++ 程序員多年的問題,它無法徹底根治,只能去盡力避免。

後來的很多高級語言,比如 Java、Python、JavaScript、C#、PHP、Go 等,都增加了垃圾內存回收機制,這從根本上避免了內存泄露,程序員再也不用擔心內存問題了。

不過垃圾內存回收是有代價的,你需要對內存進行標記,並及時改變它的值,另外還需要額外增加一個線程或者進程,對內存進行實時監控,發現沒用的內存就及時回收。