程序使用記憶體概念
對普通程序來說,能看到的其實是核心提供的虛擬記憶體,這些虛擬記憶體還需要通過頁表,由系統對映為物理記憶體。當程序通過 malloc() 申請虛擬記憶體後,系統並不會立即為其分配物理記憶體,而是在首次訪問時,才通過缺頁異常陷入核心中分配記憶體。為了協調 cpu 與磁碟間的效能差異,linux 還會使用 cache 和 buffer ,分別把檔案和磁碟讀寫的資料快取到記憶體中。對應用程式來說,動態記憶體的分配和**,是既核心又複雜的乙個邏輯功能模組。管理記憶體的過程中,也很容易發生各種各樣的「事故」,比如,沒正確**分配後的記憶體,導致了洩漏。訪問的是已分配記憶體邊界外的位址,導致程式異常退出,等等。
記憶體的分配和**,過程中造成記憶體洩漏的問題分析
程序的記憶體空間時,使用者空間記憶體包括多個不同的記憶體段,比如唯讀段、資料段、堆、棧以及檔案對映段等。這些記憶體段正是應用程式使用記憶體的基本方式。
在程式中定義了乙個區域性變數,比如乙個整數陣列 int data[64] ,就定義了乙個可以儲存 64 個整數的記憶體段。由於這是乙個區域性變數,它會從記憶體空間的棧中分配記憶體。
棧記憶體由系統自動分配和管理。一旦程式執行超出了這個區域性變數的作用域,棧記憶體就會被系統自動**,所以不會產生記憶體洩漏的問題。
很多時候,並不知道資料大小,所以就要用到標準庫函式 malloc() _,_ 在程式中動態分配記憶體。這時候,系統就會從記憶體空間的堆中分配記憶體。堆記憶體由應用程式自己來分配和管理。除非程式退出,這些堆記憶體並不會被系統自動釋放,而是需要應用程式明確呼叫庫函式 free() 來釋放它們。如果應用程式沒有正確釋放堆記憶體,就會造成記憶體洩漏。
唯讀段,包括程式的**和常量,由於是唯讀的,不會再去分配新的記憶體,所以也不會產生記憶體洩漏。
資料段,包括全域性變數和靜態變數,這些變數在定義時就已經確定了大小,所以也不會產生記憶體洩漏。
最後乙個記憶體對映段,包括動態鏈結庫和共享記憶體,其中共享記憶體由程式動態分配和管理。所以,如果程式在分配後忘了**,就會導致跟堆記憶體類似的洩漏問題。
記憶體洩漏的危害非常大
這些忘記釋放的記憶體,不僅應用程式自己不能訪問,系統也不能把它們再次分配給其他應用。記憶體洩漏不斷累積,甚至會耗盡系統記憶體。
雖然,系統最終可以通過 oom (out of memory)機制殺死程序,但程序在 oom 前,可能已經引發了一連串的反應,導致嚴重的效能問題。其他需要記憶體的程序,可能無法分配新的記憶體;記憶體不足,又會觸發系統的快取**以及 swap 機制,從而進一步導致 i/o 的效能問題等等。
就用乙個計算斐波那契數列的案例,來看看記憶體洩漏問題的定位和處理方法
斐波那契數列是乙個這樣的數列:0、1、1、2、3、5、8…,也就是除了前兩個數是 0 和 1,其他數都由前面兩數相加得到,用數學公式來表示就是 f(n)=f(n-1)+f(n-2),(n>=2),f(0)=0, f(1)=1。
實驗
機器配置:2 cpu,8gb 記憶體預先安裝 sysstat、docker 以及 bcc 軟體包,
sysstat 軟體包中的 vmstat ,可以觀察記憶體的變化情況;而 docker 可以執行案例程式。
確認案例應用已經正常啟動。如果一切正常,應該可以看到下面這個介面:
2th => 1
3th => 2
4th => 3
5th => 5
6th => 8
7th => 13
執行下面的 vmstat ,等待一段時間,觀察記憶體的變化情況。
# 每隔3秒輸出一組資料$ vmstat 3
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 6601824 97620 1098784 0 0 0 0 62 322 0 0 100 0 0
0 0 0 6601700 97620 1098788 0 0 0 0 57 251 0 0 100 0 0
0 0 0 6601320 97620 1098788 0 0 0 3 52 306 0 0 100 0 0
0 0 0 6601452 97628 1098788 0 0 0 27 63 326 0 0 100 0 0
2 0 0 6601328 97628 1098788 0 0 0 44 52 299 0 0 100 0 0
0 0 0 6601080 97628 1098792 0 0 0 0 56 285 0 0 100 0 0
從輸出中可以看到,記憶體的 free 列在不停的變化,並且是下降趨勢;而 buffer 和 cache 基本保持不變。
未使用內存在逐漸減小,而 buffer 和 cache 基本不變,這說明,系統中使用的記憶體一直在公升高。但這並不能說明有記憶體洩漏,因為應用程式執行中需要的記憶體也可能會增大。比如說,程式中如果用了乙個動態增長的陣列來快取計算結果,占用記憶體自然會增長。
專門用來檢測記憶體洩漏的工具,memleak。memleak 可以跟蹤系統或指定程序的記憶體分配、釋放請求,然後定期輸出乙個未釋放記憶體和相應呼叫棧的彙總情況(預設 5 秒)。
memleak 是 bcc 軟體包中的乙個工具,一開始就裝好了,執行 /usr/share/bcc/tools/memleak 就可以執行它
# -a 表示顯示每個記憶體分配請求的大小以及位址# -p 指定案例應用的pid號
addr = 7f8f704732b0 size = 8192
addr = 7f8f704772d0 size = 8192
addr = 7f8f704712a0 size = 8192
addr = 7f8f704752c0 size = 8192
32768 bytes in 4 allocations from stack
start_thread+0xdb [libpthread-2.27.so]
attaching to pid 12512, ctrl+c to quit.
[03:00:41] top 10 stacks with outstanding allocations:
addr = 7f8f70863220 size = 8192
addr = 7f8f70861210 size = 8192
addr = 7f8f7085b1e0 size = 8192
addr = 7f8f7085f200 size = 8192
addr = 7f8f7085d1f0 size = 8192
40960 bytes in 5 allocations from stack
start_thread+0xdb [libpthread-2.27.so]
...long long *fibonacci(long long *n0, long long *n1)
void *child(void *arg)
}...
會發現, child() 呼叫了 fibonacci() 函式,但並沒有釋放 fibonacci() 返回的記憶體。所以,想要修復洩漏問題,在 child() 中加乙個釋放函式就可以了,比如:
void *child(void *arg)}
修復後重新執行
# 清理原來的案例應用# 執行修復後的應用
# 重新執行 memleak工具檢查記憶體洩漏情況
attaching to pid 18808, ctrl+c to quit.
[10:23:18] top 10 stacks with outstanding allocations:
[10:23:23] top 10 stacks with outstanding allocations:
malloc() 和 free() 通常並不是成對出現,在每個異常處理路徑和成功路徑上都釋放記憶體 。在多執行緒程式中,乙個執行緒中分配的記憶體,可能會在另乙個執行緒中訪問和釋放。更複雜的是,在第三方的庫函式中,隱式分配的記憶體可能需要應用程式顯式釋放。所以,為了避免記憶體洩漏,最重要的一點就是養成良好的程式設計習慣,比如分配記憶體後,一定要先寫好記憶體釋放的**,再去開發其他邏輯。還是那句話,有借有還,才能高效運轉,再借不難。當然,如果已經完成了開發任務,你還可以用 memleak 工具,檢查應用程式的執行中,記憶體是否洩漏。如果發現了記憶體洩漏情況,再根據 memleak 輸出的應用程式呼叫棧,定位記憶體的分配位置,從而釋放不再訪問的記憶體。
記憶體洩漏排查
在工作中發現乙個tuexdo服務存在記憶體洩漏的情況,之前也嘗試過用valgrind等工具查詢,但是因為 直接載入在tuexdo的服務中,不知道怎麼直接啟動,所以沒有用valgrind。在經過查詢資料後,決定自己寫重寫malloc free等函式,列印出分配位址和釋放位址,進行對比,如果發現只有ma...
iOS 記憶體洩漏排查方法
動態分析方法 instrument工具庫里的leaks 點選左上角的紅色圓點,這時專案開始啟動了,由於leaks是動態監測,所以手動進行一系列操作,可檢查專案中是否存在記憶體洩漏問題。如圖所示,橙色矩形框中所示綠色為正常,如果出現如右側紅色矩形框中顯示紅色,則表示出現記憶體洩漏。選中leaks ch...
c 記憶體洩漏排查簡單完美
callocbuffer cb char str cb.callocchar 2048,function cb.freechar 這裡注釋掉會列印記憶體沒有釋放 h class callocbuffer cpp include callocbuffer.h include callocbuffer ...