記憶體洩漏了,該如何定位

2021-10-25 16:20:42 字數 4141 閱讀 8149

程序的使用者空間記憶體包括了多個不同的記憶體段,比如唯讀段、資料段、堆、棧、以及檔案對映等,這些記憶體段是應用程式使用記憶體的基本方式。

比如再程式中定義乙個區域性變數, int data[64] ,就定義了乙個可以儲存64個整數的記憶體段,由於是區域性變數,會從記憶體空間的棧中分配。

記憶體洩漏的危害非常大,忘記釋放的記憶體,不僅應用程式不能訪問,系統也不能把它們再次分配給其他應用,記憶體洩漏不斷積累,最終耗盡系統記憶體。

雖然系統可以oom機制殺死程序,但程序在oom前,會引發一連串的反應,導致嚴重的效能問題。

其他需要記憶體的程序,可能無法分配新的記憶體,記憶體不足,又會觸發系統的快取**以及swap機制,從而進一步導致io效能問題。

用乙個計算菲波那切數列的案例,看下記憶體洩漏問題的定位和處理方法。

斐波那契數列是乙個這樣的數列: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。

執行下面的命令來執行案例:

案例成功執行後,你需要輸入下面的命令,確認案例應用已經正常啟動。如果一切正常,你應該可以看到下面這個介面:

2th =

> 1

3th =

> 2

4th =

> 3

5th =

> 5

6th =

> 8

7th =

> 13

從輸出中,我們可以發現,這個案例會輸出斐波那契數列的一系列數值。實際上,這些數值每隔 1 秒輸出一次。

執行下面的 vmstat ,等待一段時間,觀察記憶體的變化情況。如果忘了 vmstat 裡各指標的含義,記得複習前面內容,或者執行 man 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

[unknown]

[unknown]

start_thread+0xdb [libpthread-2.27.so]

從 memleak 的輸出可以看到,案例應用在不停地分配記憶體,並且這些分配的位址沒有被**。

比方說,在終端中直接執行 ls 命令,你會發現,這個路徑的確不存在:

: no such file or directory

-aattaching 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]

這一次,我們終於看到了記憶體分配的呼叫棧,原來是 fibonacci() 函式分配的記憶體沒釋放。

...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() 來 _ 釋放它們。

今天的案例比較簡單,只用加乙個 free() 呼叫就能修復記憶體洩漏。不過,實際應用程式就複雜多了。比如說,

所以,為了避免記憶體洩漏,最重要的一點就是養成良好的程式設計習慣,比如分配記憶體後,一定要先寫好記憶體釋放的**,再去開發其他邏輯。還是那句話,有借有還,才能高效運轉,再借不難。

當然,如果已經完成了開發任務,你還可以用 memleak 工具,檢查應用程式的執行中,記憶體是否洩漏。如果發現了記憶體洩漏情況,再根據 memleak 輸出的應用程式呼叫棧,定位記憶體的分配位置,從而釋放不再訪問的記憶體。

Linux 記憶體洩漏了,我該如何定位和處理?

對普通程序來說,能看到的其實是核心提供的虛擬記憶體,這些虛擬記憶體還需要通過頁表,由系統對映為物理記憶體。當程序通過 malloc 申請虛擬記憶體後,系統並不會立即為其分配物理記憶體,而是在首次訪問時,才通過缺頁異常陷入核心中分配記憶體。為了協調 cpu與磁碟間的效能差異,linux 還會使用cac...

C 該如何記憶體洩漏檢測

mfc每乙個cpp都有以下內容 ifdef debug define new debug new endif 而debug new這個巨集定義在afx.h中,就是它幫助我們解決這個問題這樣如果洩露就可以通過輸出視窗看見。其中最重要的乙個是在標頭檔案中包含crtdbg.h 然後呼叫 crtdumpme...

C 記憶體洩漏定位 valgrind

new 和 delete 不匹配導致記憶體洩漏。最開始因為指標被我不小心delete了,導致有錯誤,就遮蔽了。網上學習了用valgrind 定位洩漏記憶體的位置,比較方便。然後,編譯源程式,比如 g g o testexe testexe.cpp 然後利用valgrind 進行定位,可執行程式可能有...