使用UDT庫,編寫簡單的網絡通信程序,發現了一個問題,關閉一部分連接後,程序占用內存並沒有變化。
比如先連接500個,再連接另500個,先關掉後面500個,程序占用內存降一半,再關掉500個,程序占用內存降到0.1。然而,如果先關掉前面500個,程序占用內存不會發生變化,只有等再關掉後面500個,程序內存才會降到0.1。
換個順序就降不了,這很奇怪,很“玄學”。
跟蹤代碼至底層,該有的釋放都有,這是為什麼?靈機一動想到可能與linux內存管理機制有關,果不其然,linux中給程序分配堆內存後,當程序free,內存並不會馬上還給系統,而是交由Ptmalloc管轄,這樣程序再需要內存的時候,就可以直接向Ptmalloc取,不用再向系統申請,效率較高。Ptmalloc也不是說就不把內存還給系統了,返回內存的機制叫“內存收縮”,當堆頂的空閒內存大於收縮阈值(默認是128KB)時,即可觸發。注意,這邊要求的空閒內存必須位於堆頂,所以如果堆頂的內存不釋放,堆底的內存再怎麼釋放都觸發不了內存收縮,這就導致了那個玄學的結果。
因此,在代碼裡添加這麼一句mallopt(M_MMAP_THRESHOLD, 128);,M_MMAP_THRESHOLD是mmap分配阈值,當程序所要內存大於M_MMAP_THRESHOLD時,直接調用mmap()分配內存,free的時候,會直接調用munmap()將內存還給操作系統,不再被Ptmalloc緩存管理。所以連接關閉後,內存就能下去了。
這樣做的缺點是需要的內存更多,需要的內存數量隨M_MMAP_THRESHOLD數值的減少而增多,所以需要選個合適的數字。且這些內存不再重用,分配效率也會比較低。
然而,對於分配長生命周期的大內存塊,使用mmap()才是最高效的,Ptmalloc並不擅長管理長生命周期的內存,尤其是持續不定期分配和釋放長生命周期的內存,這會導致內存暴增。