內核和處理器負責將虛擬內存映射到物理內存。爲了提升效率,會在稱爲頁面的內存組中建立內存映射,其中每一個頁面的大小是處理器的詳細信息。儘管大多數處理器也支持更大的容量,但一般有4 KB,Linux稱其爲 hugepage大頁面。內核能夠從其本身的空閒列表中爲物理內存頁面請求提供服務,內核爲每一個DRAM組和CPU維護這些請求以提升效率。內核本身的軟件也一般經過內核分配器(例如slab分配器)從這些空閒列表中消耗內存。html
內存頁和交換java
典型的用戶內存頁面的生命週期如圖7-2所示,其中列舉了如下步驟:shell
1. 應用程序從內存分配請求開始(例如,libc malloc() )。api
2. 分配庫能夠從其本身的空閒列表中爲內存請求提供服務,或者它可能須要擴展虛擬內存來容納。根據分配庫,它將:緩存
1. 經過調用brk() syscall並將堆內存用於分配來擴展堆的大小。bash
2. 經過mmap() 系統調用建立一個新的內存段。服務器
3. 稍後,應用程序嘗試經過存儲和加載指令使用分配的內存範圍,這涉及調用處理器內存管理單元(MMU)進行虛擬到物理地址的轉換。至此,虛擬內存的謊話就暴露出來了:該地址沒有映射!這會致使稱爲頁面錯誤的MMU錯誤。app
4. 頁面錯誤由內核處理,內核創建從其物理內存可用列表到虛擬內存的映射,而後將該映射通知MMU以供之後查找。如今,該過程佔用了額外的物理內存頁面。進程使用的物理內存量稱爲其駐留集大小(RSS)。ide
5. 當系統上的內存需求過多時,內核頁面輸出守護程序(kswapd)可能會尋找可用的內存頁面。它將釋放三種類型的內存中的一種(儘管只有(c)如圖7-2所示,由於它顯示了用戶內存頁面的生命週期):函數
1. 從磁盤讀取但未修改的文件系統頁面(稱爲「由磁盤支持」):能夠當即釋放這些頁面,並在須要時簡單地從新讀取。這些頁面是應用程序可執行的文本,數據和文件系統元數據。
2. 已修改的文件系統頁面:這些是「髒」的,必須先寫入磁盤,而後才能釋放它們。
3. 應用程序內存頁面:因爲它們沒有文件來源,所以被稱爲匿名內存。若是正在使用交換設備,則能夠先將它們存儲在交換設備上來釋放它們。將頁面寫到交換設備稱爲交換(在Linux上)。
內存分配請求一般是頻繁的活動:對於繁忙的應用程序,用戶級別的分配每秒可能發生數百萬次。加載和存儲指令以及MMU查找更加頻繁。它們每秒可能發生數十億次。在圖7-2中,這些箭頭以粗體顯示。其餘活動相對較少:brk()和mmap()調用,頁面錯誤和頁面退出(較亮的箭頭)。
page-out daemon頁面輸出守護程序
按期激活頁面輸出守護程序(kswapd)以掃描非活動和活動頁面的LRU列表,以尋找可用的內存。如圖7-3所示,當空閒內存越太低閾值時它將被喚醒,而當空閒內存越太高閾值時將回到睡眠狀態。
kswapd協調後臺頁面調出;除了CPU和磁盤I/O爭用外,這些不該直接損害應用程序性能。若是kswapd沒法足夠快地釋放內存,則會超過可調的最小頁面閾值,並使用直接回收;這是釋放內存以知足分配條件的前臺模式。在這種模式下,分配阻塞(停頓)並同步等待頁面被釋放。
直接回收能夠調用內核模塊收縮器函數:這些釋放的內存可能保留在緩存中的內存,包括內核slab緩存。
swap devices交換設備
交換設備爲內存不足的系統提供了降級的操做模式:進程能夠繼續分配,可是如今將不常使用的頁面移入和移出交換設備,這一般會使應用程序運行得慢得多。
一些生產系統無需交換便可運行;這樣作的理由是,對於那些關鍵系統來講,降級的操做模式是永遠沒法接受的,由於這些關鍵系統可能有許多冗餘(且運行情況良好)服務器,比開始交換的服務器要好用得多。(例如,對於Netflix雲實例,一般就是這種狀況。)
若是無交換系統的內存不足,則內核oom killer會犧牲一個進程。爲了不這種狀況,將應用程序配置爲永不超過系統的內存限制。
oom killer
Linux內存不足殺手是釋放內存的最後手段:它將使用啓發式方法找到受害者進程,並經過殺死它們來犧牲它們。啓發式尋找將釋放許多頁面的最大受害者,而且這不是關鍵任務,例如內核線程或init(PID 1)。Linux提供了在整個系統和每一個進程中調整OOM殺手的行爲的方法。
page compaction頁面壓縮
隨着時間的流逝,釋放的頁面變得碎片化,從而使內核很難根據須要分配較大的連續塊。內核使用壓縮程序來移動頁面,從而釋放連續區域。
file system caching and buffering文件系統緩存和緩衝
Linux借用空閒內存進行文件系統緩存,並在有需求時將其恢復爲空閒狀態。這種借用的結果是,在Linux啓動以後,系統報告的可用內存趨向於零,這可能使用戶擔憂系統實際上只是在預熱其文件系統緩存時會耗盡內存。此外,文件系統使用內存進行回寫緩衝(write-back buffering)。
能夠將Linux調整爲更喜歡從文件系統緩存中釋放或經過交換釋放內存(經過調整參數vm.swappiness)。
傳統的分析工具
傳統的性能工具提供了許多基於容量的內存使用狀況統計信息,包括每一個進程和系統範圍內使用了多少虛擬和物理內存,以及某些細分,例如按流程段或面板。分析內存使用率超出基本知識,例如頁面錯誤率,分配庫,運行時或應用程序對每一個分配都須要內置的工具;或者可使用像Valgrind這樣的虛擬機分析器;後一種方法可能會致使目標應用程序在檢測時運行速度慢10倍以上。BPF工具效率更高,開銷也更小。
Tool |
Type |
Description |
dmesg |
Kernel log |
OOM killer event details |
swapon |
Kernel statistics |
Swap device usage |
free |
Kernel statistics |
System-wide memory usage |
ps |
Kernel statistics |
Process statistics, including memory usage |
pmap |
Kernel statistics |
Process memory usage by segment |
vmstat |
Kernel statistics |
Various statistics, including memory |
sar |
Kernel statistics |
Can show page fault and page scanner rates |
perf |
Software events, hardware statistics, hardware sampling |
Memory-related PMC statistics and event sampling |
用於內存分析相關的BPF工具
內存相關的工具:
Tool |
Source |
Target |
Description |
oomkill |
BCC/BT |
OOM |
Shows extra info on OOM kill events 顯示oom相關的事件 |
memleak |
BCC |
Sched |
Shows possible memory leak code paths 顯示可能的內存泄漏代碼路徑 |
mmapsnoop |
Book |
Syscalls |
Traces mmap(2) calls system-wide 跟蹤系統範圍內的mmap調用 |
brkstack |
Book |
Syscalls |
Shows brk() calls with user stack traces 顯示帶有用戶堆棧跟蹤的brk()調用 |
shmsnoop |
BCC |
Syscalls |
Traces shared memory calls with details 跟蹤共享內存調用的詳細信息 |
faults |
Book |
Faults |
Shows page faults, by user stack trace 經過用戶堆棧跟蹤顯示頁面錯誤 |
ffaults |
Book |
Faults |
Shows page faults, by filename 經過文件名顯示頁面錯誤 |
vmscan |
Book |
VM |
Measures VM scanner shrink and reclaim times 測量vm scaner的收縮和回收時間 |
drsnoop |
BCC |
VM |
Traces direct reclaim events, showing latency 跟蹤直接回收事件,顯示延遲 |
swapin |
Book |
VM |
Shows swap-ins by process 按進程顯示swap狀況 |
hfaults |
Book |
Faults |
Shows huge page faults, by process 按進程顯示巨頁錯誤狀況 |
此外,還有幾個用於內存分析的BPF工具: kmem 、kpages 、 slabratetop 、 numamove
oomkill是一個BCC和bpftrace工具,用於跟蹤內存不足殺手事件並打印詳細信息(例如平均負載)。平均負載爲OOM時的系統狀態提供了一些額外的上下文,顯示了系統是否正在變得忙碌或穩定。
此輸出代表PID 18601(perl)須要內存,這觸發了PID 1165(java)的OOM終止。PID 1165的內存佔用已達到18006224個pages;這些一般每頁4 KB,具體取決於處理器和進程內存設置。loadavg平均負載代表,在OOM終止時,系統變得更加繁忙。
該工具經過使用kprobes跟蹤oom_kill_process() 函數並打印各類細節來工做。在這種狀況下,只需讀取/proc/loadavg便可獲取平均負載。調試OOM事件時,能夠根據須要加強此工具以打印其餘詳細信息。此外,此工具還沒有使用能夠顯示有關如何選擇任務的更多詳細信息的oom跟蹤點。
memleak是一個BCC工具,可跟蹤內存分配和空閒事件以及分配堆棧跟蹤。隨着時間的流逝,它能夠顯示長期倖存者-還沒有釋放的分配。
此示例顯示了在bash shell進程上運行的memleak:
僅memleak不能告訴您這些分配是不是真正的內存泄漏(內存泄漏:指的是沒有引用而且永遠不會釋放的已分配內存),內存增加仍是長期分配。爲了區分它們,須要研究和理解代碼路徑。
固然,還有不少內存分析的小工具。這裏就不一一列舉了。具體能夠看brendangregg大佬的新書。