日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一次“内存泄露”引发的血案

發布時間:2024/1/23 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一次“内存泄露”引发的血案 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2017年末,手Q春節紅包項目期間,為保障活動期間服務正常穩定,我對性能不佳的Ark Server進行了改造和重寫。重編發布一段時間后,結果發現新發布的Svr的機器內存一直在上漲。如下圖示:

觀察后,第一反應是完了,一定存在內存泄露。花了3、4天時間,使用各種辦法進行定位,一無所獲。

后來無意中在SPP日志中發現了端倪,日志中一直打印tcp socket[%d] user check pkg not ok, but no more memory,看代碼邏輯,是收包緩沖區太小,導致調用方不斷使用new操作來擴充緩沖區,我仔細檢查了下調用方的代碼邏輯,使用的是SPP微線程架構,收包緩沖區是一個Msg的局部變量,在Msg析構時,都會調用delete,換而言之,這里絕不可能存在內存泄露。

既然不存在內存泄露,內存為什么會一直漲呢?按照

上圖來看,內存在1天內漲了1G左右,這個速度也太可怕了吧。既然唯一的線索在內存分配操作new和delete上,那么只可能是這里有貓膩。
網上搜索了下delete not return memory,果然說來話長啊。下面我們就來回顧下C++程序中的內存管理機制


物理內存、虛擬內存

物理內存好說,就是機器的真實內存,你機器是多大內存條,物理內存就多大。虛擬內存(虛擬地址空間)是一個邏輯概念,32bit下每個進程都有4G虛擬地址空間,而且每個進程間的地址空間相互獨立。
從進程的角度來說,每個進程均認為自己獨享整個內存空間(4G)。進程空間分布如下圖:

如上圖示:最高的1G空間保留給內核使用。接下來是棧,棧向低地址方向延伸(棧的大小受RLIMIT_STACK限制,默認為8M),下面是MMAP區(文件映射內存,如動態庫等,SPP微線程的私有棧也位于這里)下面是堆(動態內存增長),堆向高地址方向延伸,接下來依次是BSS、數據段、代碼段。

需要注意的一點是:上面所說的都是虛擬內存。只有在真正使用到這片內存空間時,才會涉及到物理內存頁的分配等(內核管理,頁錯誤)。

Linux下動態內存分配實現機制

C、C++的動態內存分配、管理都是基于malloc和free的,動態內存即虛擬空間堆區。另外多說一句,malloc和free操作的也是虛擬地址空間。

malloc,動態內存分配函數。是通過brk(sbrk)和mmap這兩個系統調用實現的。

結合上文進程虛擬空間圖,brk(sbrk)是將數據段(.data)的最高地址指針_edata往高地址推。mmap是在進程的虛擬地址空間中(堆和棧中間,稱為文件映射區域的地方)找一塊空閑的虛擬內存。這兩種實現方式的區別大致如下:

1.brk(sbrk),性能損耗少;mmap相對而言,性能損耗大

2.mmap不存在內存碎片(是物理頁對齊的,整頁映射和釋放);brk(sbrk)可能存在內存碎片(由于new和delete的順序不同,可能存在空洞,又稱為碎片)

無論是通過brk(sbrk)還是mmap調用分配的內存都是虛擬空間的內存,只有在第一次訪問已分配的虛擬地址空間的時候,發生缺頁終端,操作系統負責分配物理內存,然后建立虛擬內存和物理內存之間的映射關系。

delete,動態內存釋放函數。如果是brk(sbrk)分配的內存,直接調用brk(sbrk)并傳入負數,即可縮小Heap區的大小;如果是mmap分配的內存,調用munmap歸還內存。無論這兩種那種處理方式,都會立即縮減進程虛擬地址空間,并歸還未使用的物理內存給操作系統。

?

brk(sbrk)和mmap都是系統調用,如果程序中頻繁的進行內存的擴張和收縮,每次都直接調用,當然可以實現內存精確管理的目的,但是隨之而來的性能損耗也很顯著。目前大多數運行庫(glibc)等對內存管理做了一層封裝,避免每次直接調用系統調用影響性能。如此,就設計到運行庫的內存分配的算法問題了。

在標準C庫中,提供了malloc/free函數分配釋放內存,這兩個函數底層是由brk,mmap,munmap這些系統調用實現的。

如何查看進程發生缺頁終端的次數?

用ps -o majflt,minflt -C program命令查看。

majflt代表major fault,中文名叫大錯誤,minflt代表minor fault,中文名叫小錯誤。這兩個數值表示一個進程自啟動以來所發生的缺頁中斷的次數。

發生缺頁中斷后,執行了哪些操作?

當一個進程發生缺頁中斷的時候,進程會陷入內核態,執行一下操作:

1、檢察要訪問的虛擬地址是否合法。

2、查找/分配一個物理頁

3、填充物理頁內容(讀取磁盤,或者直接置0,或者啥也不干)

4、建立映射關系(虛擬地址到物理地址)

重新執行發生缺頁終端的那條指令

如果第三步,需要讀取磁盤,那么這次缺頁中斷就是majflt,否則就是minflt。

查看物理內存使用情況: cat /proc/$PID/smaps,里面詳細記錄了該進程使用的物理頁內存情況,如Private_Dirty、Private_Clean等

mmap系統調用:讀寫MMAP映射區,相當于讀寫被映射的文件。本意是將文件當做內存一樣讀寫。相比Read、Write,減少了內存拷貝(Read、Write一個硬盤文件,需要先將數據從內核緩沖區拷貝到應用緩沖區(read),然后再將數據從應用緩沖區拷貝回內核緩沖區(write)。mmap直接將數據從內核緩沖區映射拷貝到另一個內核緩沖區),但是被修改的數據從MMAP區同步到磁盤文件上,依賴于系統的頁管理算法,默認hi慢條斯理的將內容寫到磁盤上。另外提供了msync強制同步到磁盤上。

Glibc內存分配算法

glibc的內存分配算法,是基于dlmalloc實現的ptmalloc,dlmalloc詳細可以參考A Memory Allocator或者我之前的文章Glibc內存分配器。這里主要講下和內存歸還策略相關的,其他內容不做過多擴展。

整體來說,glibc采用的是dlmalloc。為了避免頻繁調用系統調用,它內部維護了一個內存池,方便reuse,又稱為free-list或bins,如下圖所示:

所有調用delete釋放的內存,并不是立即調用brk(sbrk)歸還給操作系統,而是先將這個內存塊掛在free-list(bins)里面,然后進行內存歸并(可選操作,相鄰的可用內存塊合并為更大的可用內存塊),并檢查是否達到malloc_trim的threshhold,如果達到了,則調用malloc_trim歸還部分可用內存給操作系統。

glibc中,設置了默認進行malloc_trim的threshhold為128K,也就是說當dlmalloc管理的內存池中最大可用內存>128K時,就會執行malloc_trim操作,歸還部分內存給操作系統;而在可用內存<=128K時,及時程序中delete了這部分內存,這些內存也是不歸還給操作系統的。表現為:調用delete之后,進程占用的內存并沒有減少。

另外,部分glibc的默認設置如下:

DEFAULT_MXFAST 64 (for 32bit), 128(for 64bit) //free-list(fastbin)最大內存塊 DEFAULT_TRIM_THRESHOLD 128 * 1024 // malloc_trim的門檻值 128k DEFAULT_TOP_PAD 0 DEFAULT_MMAP_THRESHOLD 128 * 1024 // 使用mmap分配內存的門檻值 128k DEFAULT_MMAP_MAX 65536 // mmap的最大數量

這些參數都可以通過mallopt進行調整。

malloc_trim(0)可以立即執行trim操作,將內存還給操作系統。

具體fastbin相關的內容,此處不做介紹,前期有很多基于fastbin的堆溢出攻擊,感興趣的同學可以google關鍵字fastbin搜索下。

測試:

1.循環new分配64K * 2048的內存空間,寫入臟數據后,循環調用delete釋放。top看進程依然使用131M內存,沒有釋放。? ---- 此時用brk

2.循環new分配128K * 2048的內存空間,寫入臟數據后,循環調用delete釋放。top看進程使用,2960字節內存,完全釋放。? ?----此時用mmap

3.設置M_MMAP_THRESHOLD 256k, 循環new分配128k * 2048的內存空間,寫入臟數據后,循環調用delete釋放,而后調用malloc_trim(0).top看進程使用,2348字節,完全釋放。? ----此時用brk

?

64k Delete前內存占用:

64k Delete后內存占用:

128k Delete前內存占用:

128k Delete后內存占用:

測試代碼如下:

int main(int argc, char *argv[]) {mallopt(M_MMAP_THRESHOLD, 256*1024);//mallopt(M_TRIM_THRESHOLD, 64*1024);//MemoryLeakint MEMORY_SIZE = hydra::CTrans::STOI(argv[1]);vector<char *> Array;for (int j=0; j<2064; j++) {char *Buff = new char[MEMORY_SIZE];for (int i=0; i<MEMORY_SIZE; i++) Buff[i] = i;Array.push_back(Buff);}sleep(10);for (int j=0; j<2065; j++) delete []Array[j];cout << "Delete All" << endl;//sleep(10);//malloc_trim(0);//cout << "strim" << endl;while(1) sleep(10); }

一個例子來說明內存分配的原理

情況下、malloc小于128k的內存,使用brk分配內存,將_edata往高地址推(只分配虛擬空間,不對應物理內存(因此沒有初始化),第一次讀/寫數據時,引起內核缺頁中斷,內核才分配對應的物理內存,然后虛擬地址空間建立映射關系),如下圖:

1.進程啟動的時候,其(虛擬)內存空間的初始布局如圖1所示。

其中,mmap內存映射文件是在堆和棧的中間(例如libc-2.2.93.so,其他數據文件等),為了簡單起見,省略了內存映射文件。

_edata指針(glibc里面定義)指向數據段的最高地址。

2.進程調用A=malloc(30k以后,內存空間如圖2:

malloc調用會調用brk系統調用,將_edata指針往高地址推30K,就完成虛擬內存分配。

你可能會問:只要把_edata+30K就完成內存分配了?

事實是這樣的,_edata_30K只是完成虛擬地址的分配,A這塊內存現在還是沒有物理頁與之對應的,等到進程第一次讀寫A這塊內存的時候,發生缺頁中斷,這個時候,內核才分配A這塊內存對應的物理頁。也就是說,如果用malloc分配了A這塊內容,然后從來不訪問它,那么,A對應的物理頁是不會被分配的。

3.進程調用B=malloc(40K)以后,內存空間如圖3.

情況二、malloc大于128k的內存,使用mmap分配內存,在堆和棧之間找一塊空閑內存分配(對應獨立內存,而且初始化為0),如下圖:

4.進程調用C=malloc(200K)以后,內存空間如圖4:

默認情況下,malloc函數分配內存,如果請求內存大于128k(可由M_MAP_THRESHOLD選項調節),那就不是去推_edata指針了,而是利用mmap系統調用,從堆和棧的中間分配一塊虛擬內存。

這樣子做主要是因為::brk分配的內存需要等到高地址內存釋放以后才能釋放(例如,在B釋放之前,A是不可能釋放的,這就是內存碎片產生的原因,什么時候緊縮看下面),而mmap分配的內存可以單獨釋放。

當然,還有其他的好處,也有壞處,再具體下去,有興趣的同學可以去看glibc里面malloc的代碼了。

5.進程調用D=malloc(100K)以后,內存空間如圖5;

6.進程調用free(C)以后,C對應的虛擬內存和物理內存一起釋放。

7.進程調用free(B)以后,如圖7所示:

B對應的虛擬內存和物理內存都沒有釋放,因為只有一個_edata指針,如果往回推,那么D這塊內存怎么辦呢?當然,B這塊內存,是可以重用的,如果這個時候再來一個40K的請求,那么malloc很可能就把B這塊內存返回回去了。

8.進程調用free(D)以后,如圖8所示:

B和D連接起來,變成了一塊140K的空閑內存。

9.默認情況下:

? 當最高地址空閑的空閑內存超過128K(可由M_TRIM_THRESHOLD選項調節)時,執行內存緊縮操作(trim)。在上一個步驟free的時候,發現最高地址空閑內存超過128K,于是內存緊縮,變成圖9所示。


結論

簡單來說,文章開頭內存不斷增長的趨勢的根本原因是:glibc在利用操作系統的內存構建進程自身的內存池。由于進程本身處理請求量大,頻繁調用new和delete,在一段時間內,進程不斷的從操作系統獲取內存來滿足新增的調用要求,但是從最終結果上來將,總有一個臨界點,使得進程從操作系統新獲取的內存和歸還操作系統的內存達成相對平衡。在這個動態平衡建立前,內存會不斷增長,直到到達臨界點。

按照這里理論,機器內存應該先漲后平。我們看下幾天后,機器的內存趨勢圖:

?

可以看出,在系統內存增長到3.7G左右時,整個機器的內存處于動態平衡的階段,不再顯著增長。由此驗證,我們的推斷是正確的。

經驗

遇到如文章開頭所說的那種內存不斷增長的情況,不要輕易斷定內存泄漏,先觀察一段時間再說。很可能是上文分析的原因。

參考文章

  • A Memory Allocator(dlmalloc, glibc)
  • Free/Delete Not Returning Memory To OS?
  • Does calling free or delete ever release memory back to the “system”
  • How is malloc() implemented internally? [duplicate]
  • How do malloc() and free() work?
  • 淺析Linux堆溢出之fastbin
  • Unix環境高級編程
  • 內存分配的原理__進程分配內存有兩種方式,分別由兩個系統調用完成:brk和mmap(不考慮共享內存)
  • ?

    總結

    以上是生活随笔為你收集整理的一次“内存泄露”引发的血案的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 亚洲大片免费观看 | 国产一区二区片 | 日本成人免费视频 | 亚洲风情亚aⅴ在线发布 | 日本少妇18p | 国产在线日韩 | 91福利一区 | 黄色激情在线 | 国产又粗又猛又爽又黄91 | 欧美日韩一区不卡 | 色婷婷中文 | 色呦呦国产精品 | 91精品国产一区二区三区 | 天天做天天摸天天爽天天爱 | 2019国产精品视频 | 中文字幕人妻熟女在线 | 影音先锋亚洲一区 | 91大神视频在线播放 | 4虎tv| 少妇脱了内裤让我添 | 国产午夜麻豆影院在线观看 | 欧美三级理论片 | 日韩在线一二三区 | 欧美大片高清 | 亚洲色综合 | 99成人在线视频 | 星空大象mv高清在线观看免费 | 国产微拍精品一区 | 国产日韩精品电影 | 91麻豆蜜桃一区二区三区 | 亚洲aav| 全部孕妇毛片丰满孕妇孕交 | 欧美少妇一区 | 久久免费高清 | 夜夜高潮夜夜爽 | 免费观看成年人网站 | 一区二区三区免费在线视频 | 丰满大乳国产精品 | 亚洲一区精品视频 | 三级黄色小视频 | 亚洲精品免费在线观看 | 99re最新| 久久看片网 | 女人扒开腿免费视频app | 红桃视频一区 | 制服丝袜第二页 | 日本午夜精品理论片a级app发布 | 人人看人人做 | 亚洲天堂一级片 | 一级片黑人 | 青青草娱乐视频 | aaa一区二区 | av免费观看网址 | 国产精品99久久久久久人 | 日本狠狠干 | 关之琳三级全黄做爰在线观看 | 超碰在线人人干 | 韩国黄色网| 夜夜夜综合 | 黄色录像网址 | 毛片无遮挡 | 亚洲v国产v欧美v久久久久久 | 67194少妇在线观看 | 极品尤物一区二区三区 | 老司机精品在线 | 亚洲男女在线观看 | 黄色欧美在线观看 | 欧美人妻少妇一区二区三区 | 动漫美女放屁 | 欧美午夜精品一区二区三区电影 | 日韩免费二区 | 日韩精品一区二区三区视频在线观看 | 69色综合 | 免费人妻精品一区二区三区 | 欧美破处女 | 午夜久久久 | 激情爱爱网站 | 日韩免费大片 | 日本做爰三级床戏 | 欧美粗大猛烈老熟妇 | 插插久久 | ,亚洲人成毛片在线播放 | 很很干很很日 | 中出亚洲 | 一级黄色免费观看 | 日美av | 日本人妖xxxx | 亚洲成人精品一区二区 | 亚洲精品88| 色婷婷狠 | 色久综合| aaa一区二区三区 | 亚洲精品少妇久久久久久 | 一级激情视频 | av白浆 | 一级毛片黄色 | 午夜精品99 | 国产精品亚洲精品 | 亚洲三级黄|