《Linux核心技術實戰課》總結二 記憶體洩漏

2021-10-11 21:44:47 字數 3343 閱讀 8784

記憶體洩漏:記憶體被分配出去後 一直沒有被釋放,導致這部分記憶體無法被再次使用,更嚴重的是,指向這塊記憶體空間的指標都不存在了,進而再也無法訪問這塊記憶體空間

場景:伺服器中的後台任務持續執行,系統中可用記憶體越來越少; 應用程式正在執行時忽然被 oom kill 掉了; 程序看起來沒有消耗多少記憶體,但是系統記憶體就是不夠用了

在遇到系統記憶體不足時,首先要做的是檢視 /proc/meminfo 中哪些記憶體型別消耗較多,然後再去做針對性分析

觀察記憶體:首先使用 top 來觀察系統所有程序的記憶體使用概況(開啟 top 後,然後按 g 再輸 入 3,從而進入記憶體模式就可以了),想要繼續看某個程序的記憶體使用細節, 你可以使用 pmap(pmap -x pidof sshd,展示sshd程序)

top是從statm來讀取資訊的,有virt/res/shr/code/data/ndrt,除了 nmaj(major page fault, 主缺頁中斷,指內容不在記憶體中然後從磁碟中來讀取的頁數)和%mem (%mem是從 res 計算而來的)

程序位址空間的分配和銷毀:

會帶來危害的程式:長時間執行+分配了記憶體沒有銷毀,很快系統記憶體就會被耗盡,進而觸發 oom killer 去殺程序,可以通過dmesg來檢視核心日誌(oom killer 選擇程序是有策略的,它未必一定會殺掉正在記憶體洩漏的程序,很有可能是一 個無辜的程序被殺掉)

系統假死場景:堆洩漏

程序 a 在申請記憶體失敗後會觸發 oom,在發生 oom 的時候會列印很多很多日誌(這些 日誌是為了方便分析為什麼 oom 會發生),然後會選擇乙個合適的程序來殺掉,從而釋放出來空閒的記憶體,這些空閒的記憶體就可以滿足後續記憶體申請了;

如果這個 oom 的過程耗時很長,其他程序(程序 b)也在此時申請記憶體,也會申請失敗,於是程序 b 同樣也 會觸發 oom 來嘗試釋放記憶體,而 oom 這裡又有乙個全域性鎖(oom_lock)來進行保護,程序 b 嘗試獲取(trylock)這個鎖的時候會失敗,就只能再次重試;

如果此時系統中有很多程序都在申請記憶體,那麼這些申請記憶體的程序都會被阻塞在這裡, 這就形成了乙個惡性迴圈,甚至會引發系統長時間無響應(假死)

解決方案:在發生 oom 時盡可能少地列印資訊、調整串列埠列印級別,不將 oom 資訊列印到串列埠

磁碟的速度是遠遠低於記憶體的,有些應用程式為了提公升效能,會避免將一些無需持續化儲存的資料寫入到磁碟,而是把這部分臨時資料寫入到記憶體中,然後定期或者在不需要這部分資料時,清理掉這部分內容來釋放出記憶體。在這種需求下,就產生了一種特殊的 shmem:tmpfs

tmpfs是一種記憶體檔案系統,只存在於記憶體中,它無需應用程式去申請和釋放記憶體,而是os自動來規劃好一部分空間,應用程式只需要往這裡面寫入資料就可以了,這樣會很方便;

就像程序往磁碟寫檔案一樣,程序寫完檔案之後就把檔案給關閉掉了,這些檔案和程序也就不再有關聯,所以這些磁碟檔案的大小不會體現在程序中;同樣地,tmpfs 中的檔案也 一樣,它也不會體現在程序的記憶體占用上

對於檔案系統,可以通過 df 來檢視它的使用情況,也可以通過 df 來看是不是 tmpfs 占用的記憶體較多,如果發現確實是它消耗了很多記憶體,那麼排查問題就變得很清晰了:分析 tmpfs 中儲存的是什麼檔案就可以

除了 tmpfs 之外,其他一些型別的記憶體也不會體現在程序記憶體中,比如:核心消耗的記憶體:/proc/meminfo 中的 slab(快取記憶體)、kernelstack(核心棧)和 vmallocused(核心通過 vmalloc 申請的記憶體),需要限制 程式所使用的這些記憶體的大小,在日誌量達到指定的大小時,自動地清理掉臨時日誌,或者定期清理掉這部分日誌,否則最終的結果就是系統可用記憶體不足,然後 觸發 oom 來殺掉程序。它很有可能會殺掉很重要的程序,或者是那些你認為不應該被殺掉的程序

oom殺程序的邏輯:

通常需要將一些很重要的系統服務的 oom_score_adj 配置為 -1000,業務服務也可以進行設定

跟使用者空間的記憶體洩漏類似,核心空間的記憶體洩漏也是指只申請記憶體而不去釋放該記憶體的情況;

不同的是,使用者空間記憶體的生命週期與使用者程序是一致的,程序退出後這部分記憶體就會自動釋放掉;

但是,核心空間記憶體的生命週期是與核心一致的,卻不是跟核心模組一致的,也就是說,在核心模組退出時,不會自動釋放掉該核心模組申請的記憶體,只有在核心重啟(即伺服器重啟)時才會釋放掉這部分記憶體,很多時候唯一的 解決方案就是重啟伺服器

核心記憶體洩漏的問題往往會發生在一些驅動程式中,比如說網絡卡驅動,ssd 卡驅動等,以 及一些第三方驅動,因為這類驅動不像 linux 核心那樣經歷過大規模的功能驗證 和測試,所以相對容易出現一些隱藏很深的問題

1 如果 /proc/meminfo 中核心記憶體(比如 vmallocused 和 sunreclaim)太大,那很有可能發生了核心記憶體洩漏;

2 周期性地觀察 vmallocused 和 sunreclaim 的變化,如果它們持續增長而不下降,也可能是發生了核心記憶體洩漏

如果想要看具體是什麼模組在使用記憶體,簡單地可以通過 /proc/vmallocinfo 來看到具體模組,比如kmem_test模組:cat /proc/vmallocinfo | grep kmem_test

如果程式很複雜,可以借助一些核心記憶體洩漏分析工具,其中最常用的分析工具就是kmemleak,推薦非生產環境:kmemleak也是通過檢查核心記憶體的申請和釋放,來判斷是否存在申請的記憶體不再使用也不釋放的情況;如果存在,就認為是核心記憶體洩漏,然後把這些洩漏的資訊通過 /sys/kernel/debug/kmemleak 這個檔案匯出給使用者分析

如果想在生產環境上來觀察核心記憶體洩漏,可以使用核心提供的核心記憶體申請釋放的 tracepoint,來動態觀察核心記憶體使用情況,使能這些 tracepoints 後,就可以觀察記憶體的動態申請和釋放情況了:

如果想要觀察某些核心結構體的申請和釋放時,可能沒有對應的 tracepiont,可以使用 kprobe 或者 systemtap,來針對具體的核心結構體申請釋放函式進行追蹤(比如通過 /proc 下面的檔案(/proc/slabinfo)判斷出來是 dentry 消耗記憶體過多的話,可以寫乙個 systemtap 指令碼來觀察 dentry 的申請和釋放)

定位出是誰在消耗記憶體

如果程序的記憶體有問題,那使用 top 就可以觀察出來;

如果程序的記憶體沒有問題, 那/proc/meminfo 可以快速定位出問題所在

定位到具體程序後,可以使用pidstat -r -p pid 1來追蹤程序的記憶體行為,如果看到增大的記憶體區域,可以通過 /proc/pid/smaps 來看是哪個區域;然後可以通過strace -t -f -p pid -o pid.strace來追蹤系統呼叫,cat pid.strace | grep 增大的大小,就可以看到出問題的系統呼叫

Redis核心技術與實戰

作為同時具備高效能 高可靠和高可擴充套件性的典型鍵值資料庫,redis不僅功能強大,而且穩定,理所當然地成為了大型網際網路公司的首選。眾多大廠在招聘的時候,不僅會要求面試者能簡單地使用redis,還要能深入地理解底層實現原理,並且具備解決常見問題的能力。可以說,熟練掌握redis已經成為了技術人的乙...

Spring框架核心技術總結

方便解耦,簡化開發 通過spring提供的 ioc容器,可以將物件間的依賴關係交由 spring 進行控制,避免硬編碼所造成的過度程式耦合。使用者也不必再為單例模式類 屬性檔案解析等這些很底層的需求編寫 可以更專注於上層的應用。依賴注入的概念 它是spring 框架核心 ioc的具體實現方式。簡單的...

C核心技術手冊(二)

組成c 程式的 積木 叫做函式,每個函式都有自己的用途,並且可以相互呼叫。每個函式包含可被執行的語句,而這些語句可以分組,從而形成語句塊。做為程式設計師,你可以直接使用 c標準庫的的函式,也可以自己編寫函式來實現既定目的。除此之外,還有很多專用的庫可以使用,例如圖形函式庫。然而,使用這些非標準庫,會...