徹底理解內存泄漏,你學會了嗎?
大家好,我是小風哥,今天和大家聊一聊內存泄漏這個話題。
在這些文章講到內存申請時我很喜歡用停車場來做類比,內存申請就好比去停車場找停車位,找到停車位后你就可以把車停在這里。
從這個類比看什么是內存泄漏呢?內存泄漏看上去是停車場的車輛只進不出導致最終找不到停車位,從程序員的角度看就是內存只申請取不釋放,如果你去問,可能有不少人認為內存泄漏就是這么回事。
然而這其實是不全面的。
申請過多內存
首先內存只申請不釋放未必就是內存泄漏,有可能是你的程序的確需要申請很多內存,這是正常的,然而如果是bug導致申請了很多內存,這就是內存泄漏了,或者也有人將其稱為space leak,意思是申請的內存超過了正常所需;不管是有意無意,總之在這種情況下你依然保持對這些內存的引用,因此你總可以找到這些內存并刪除它們,就看你刪不刪。
有很多情況會導致這一問題,像重復使用的某個結構體/對象,當再次復用時沒有清理上一次使用遺留的數(shù)據(jù)、系統(tǒng)中存在cache,但cache的過期策略設置不得當?shù)鹊取?/p>
內存無法刪除
另一類比較有趣的內存泄漏是說你申請了一些內存,但最終卻沒有什么指向它們:
void memory_leak() {
char* mem = (char*)malloc(1024);
// just return
}
在這段代碼中我們申請了1k內存,然而當memory_leak函數(shù)返回后你就再也不知道這段內存到底在哪里了!
用停車場的示例來說就是有些司機太過土豪,家里的車太多以至于把將車放在停車場這件事忘掉了,導致這些車根本就不會有人再開走,因此白白浪費停車位,并導致可用車位越來越少,而對于編程來說就是粗心大意的程序員申請了一些內存后最終“忘掉”了,再也不會有什么東西(變量/指針)指向這些內存,因此在這種情況下你沒有辦法再找到這些內存并將其刪除。
內存碎片
這也算的上是一類特殊的內存泄漏,用停車場的例子來說就是兩個停車位中間??苛艘惠v小型老年代步車,導致盡管這兩個停車位剩余的空間足夠大但又恰好都沒有辦法再??恳惠v小汽車。
假定我們系統(tǒng)中寶貴的內存大小只有8字節(jié),其中有兩個字節(jié)已經分配出去了,就像這樣:
圖片
現(xiàn)在,系統(tǒng)中空閑的內存是6字節(jié),下一次的內存申請需要分配5字節(jié),糟糕,我們已經沒有辦法再找到連續(xù)的5個字節(jié)大小的內存空間了,盡管全部空間的內存還有6字節(jié),這就是所謂的內存碎片問題。
而對于內存分配器來說如果出現(xiàn)這種情況那么將不得不借助操作系統(tǒng)的幫助來擴大堆區(qū),因此看起來我們的程序占據(jù)的內存越來越多,盡管實際上程序可能并不需要那么多內存,僅僅是因為內存碎片的原因導致一部分內存無法被再次被利用起來。
然而對于現(xiàn)代操作系統(tǒng)尤其具備虛擬內存能力的系統(tǒng)來說,內存碎片問題通常可能并不會和我們想象的那樣嚴重,原因就在于分配的內存只需要在虛擬地址空間上連續(xù)而不必在物理內存上也連續(xù),假定我們在虛擬內存地址空間需要存放“aabbccdd”這樣的字符串,在虛擬地址空間上看這是連續(xù)的就像這樣:
圖片
但在物理內存上可能是這樣存放的:
圖片
可以看到,利用虛擬內存我們可以更加充分靈活的利用“邊邊角角”的物理內存,從而減少內存碎片帶來的影響。
關于虛擬內存更詳細的講解你可以參考《深入理解操作系統(tǒng)》虛擬內存一章,關于公眾號“碼農的荒島求生”并回復“操作系統(tǒng)”即可。
如果你的程序需要重復申請很多對象/數(shù)據(jù)/結構體,并在最后一次性全部釋放,那么內存池是一個避免內存碎片不錯的選擇,原理在于盡管從內存池的角度看會有碎片,但當我們以內存池大小為單位從堆區(qū)中申請釋放內存時,這種碎片將不復存在。
內存泄漏帶來的問題
在現(xiàn)代操作系統(tǒng)中除非你的程序運行時間足夠長或者申請的內存足夠快足夠多否則內存泄漏可能并不是什么大問題,你甚至可能都察覺不出來有內存泄漏,因為當進程運行結束后其占據(jù)的內存會被操作系統(tǒng)收回,在這種情況下你可能不必過于關心這個問題,但對于長時間運行的服務器端程序、數(shù)據(jù)庫程序、操作系統(tǒng)等,內存泄漏就屬于比較嚴重的問題了,因為這些程序必須時刻在線,任何微小的內存泄漏在時間的加持下都會非常明顯。
內存持續(xù)泄漏會發(fā)生什么?
你的系統(tǒng)會慢到炸是有可能的。
內存的申請速度會對系統(tǒng)性能產生很大的影響,當系統(tǒng)內存不足時,內存分配器找到一塊滿足要求的空閑內存塊將更加困難耗時更多,當程序消耗的內存超過物理內存大小時虛擬內存系統(tǒng)(如果有的話)開始發(fā)揮作用,將進程地址空間中不常用的一部分swap出去,此時系統(tǒng)性能將快速下降,表現(xiàn)出來的就是程序員運行變慢、卡頓。
當然,根據(jù)系統(tǒng)配置,像Linux系統(tǒng),可能會將消耗內存很多的進程kill掉,這就是Out of Memory killer,簡稱oom killer。
內存泄漏檢測工具
內存泄漏問題通常比較難直接排查,尤其對于C/C++程序來說,這時我們將不得不借助必要的工具。
有一些專門的工具可以幫助你檢測內存泄漏,例如Valgrind、AddressSanitizer和MemorySanitizer。這些工具可以在運行時對程序進行檢查,識別出內存泄漏和其他內存錯誤。
此外針對特定的內存分配器,像jemalloc之類,這些內存分配器自帶內存檢測工具heap profile,能夠有效分析進程內存分配到了地方,并細化到函數(shù)級別,非常方便。