名詞解釋
記憶體主要用來儲存系統和應用程式的指令、資料、快取等。
Swap 其實就是把一塊磁碟空間當成記憶體來用。它可以把程式暫時不用的資料儲存到磁碟中(這個過程稱為換出),當程式訪問這些記憶體時,再從磁碟讀取這些資料到記憶體中(這個過程稱為換入)
linux的記憶體跟windows的很不一樣。類linux的系統會盡量使用記憶體快取東西,提供執行效率。所以linux/mac顯示的free剩餘記憶體通常很小,但實際上被快取的cache可能很大,並不代表系統記憶體緊張
記憶體對映:
- 並不是所有的虛擬記憶體都會分配實體記憶體,只有那些實際使用的虛擬記憶體才分配實體記憶體,並且分配後的實體記憶體,是通過記憶體對映來管理的。
- 記憶體對映,其實就是將虛擬記憶體地址對映到實體記憶體地址。為了完成記憶體對映,核心為每個程式都維護了一張頁表,記錄虛擬地址與實體地址的對映關係,如下圖所示:
虛擬記憶體空間分佈情況
- 首先,我們需要進一步瞭解虛擬記憶體空間的分佈情況。最上方的核心空間不用多講,下方的使用者空間記憶體,其實又被分成了多個不同的段。以 32 位系統為例,我畫了一張圖來表示它們的關係。
- 只讀段,包括程式碼和常量等
- 資料段,包括全域性變數等。
- 堆,包括動態分配的記憶體,從低地址開始向上增長。
- 檔案對映段,包括動態庫、共享記憶體等,從高地址開始向下增長。
- 棧,包括區域性變數和函式呼叫的上下文等。棧的大小是固定的,一般是 8 MB。
- 首先,我們需要進一步瞭解虛擬記憶體空間的分佈情況。最上方的核心空間不用多講,下方的使用者空間記憶體,其實又被分成了多個不同的段。以 32 位系統為例,我畫了一張圖來表示它們的關係。
記憶體分配與回收
- 對小塊記憶體(小於 128K),C 標準庫使用 brk() 來分配,也就是通過移動堆頂的位置來分配記憶體。這些記憶體釋放後並不會立刻歸還系統,而是被快取起來,這樣就可以重複使用。
- 而大塊記憶體(大於 128K),則直接使用記憶體對映 mmap() 來分配,也就是在檔案對映段找一塊空閒記憶體分配出去。
當程式通過 malloc() 申請虛擬記憶體後,系統並不會立即為其分配實體記憶體,而是在首次訪問時,才通過缺頁異常陷入核心中分配記憶體。
不僅要會用標準庫函式 malloc() 來動態分配記憶體,還要記得在用完記憶體後,呼叫庫函式 _free() 來 _ 釋放它們。
記憶體資源緊張導致的 OOM(Out Of Memory),相對容易理解,指的是系統殺死佔用大量記憶體的程式,釋放這些記憶體,再分配給其他更需要的程式。
在記憶體資源緊張時,Linux 會通過 Swap ,把不常訪問的匿名頁換出到磁碟中,下次訪問的時候再從磁碟換入到記憶體中來。你可以設定 /proc/sys/vm/min_free_kbytes,來調整系統定期回收記憶體的閾值;也可以設定 /proc/sys/vm/swappiness,來調整檔案頁和匿名頁的回收傾向。
禁止 Swap,現在伺服器的記憶體足夠大,所以除非有必要,禁用 Swap 就可以了。隨著雲端計算的普及,大部分雲平臺中的虛擬機器都預設禁止 Swap
答疑
- 只有核心才可以直接訪問實體記憶體。那麼,程式要訪問記憶體時,該怎麼辦呢?
Linux 核心給每個程式都提供了一個獨立的虛擬地址空間,並且這個地址空間是連續的。這樣,程式就可以很方便地訪問記憶體,更確切地說是訪問虛擬記憶體。
虛擬地址空間的內部又被分為核心空間和使用者空間兩部分,不同字長(也就是單個 CPU 指令可以處理資料的最大長度)的處理器,地址空間的範圍也不同。比如最常見的 32 位和 64 位系統,我畫了兩張圖來分別表示它們的虛擬地址空間,如下所示:
程在使用者態時,只能訪問使用者空間記憶體;只有進入核心態後,才可以訪問核心空間記憶體。雖然每個程式的地址空間都包含了核心空間,但這些核心空間,其實關聯的都是相同的實體記憶體。這樣,程式切換到核心態後,就可以很方便地訪問核心空間記憶體
- top 輸出介面中,跟記憶體相關的幾列資料?
- VIRT 是程式虛擬記憶體的大小,只要是程式申請過的記憶體,即便還沒有真正分配實體記憶體,也會計算在內。
- RES 是常駐記憶體的大小,也就是程式實際使用的實體記憶體大小,但不包括 Swap 和共享記憶體。
- SHR 是共享記憶體的大小,比如與其他程式共同使用的共享記憶體、載入的動態連結庫以及程式的程式碼段等。
- %MEM 是程式使用實體記憶體佔系統總記憶體的百分比。
注意:
- 第一,虛擬記憶體通常並不會全部分配實體記憶體。從上面的輸出,你可以發現每個程式的虛擬記憶體都比常駐記憶體大得多。
- 第二,共享記憶體 SHR 並不一定是共享的,比方說,程式的程式碼段、非共享的動態連結庫,也都算在 SHR 裡。當然,SHR 也包括了程式間真正共享的記憶體。所以在計算多個程式的記憶體使用時,不要把所有程式的 SHR 直接相加得出結果
- buffer 和cache
Buffer 是對磁碟資料的快取,而 Cache 是檔案資料的快取,它們既會用在讀請求中,也會用在寫請求中
- Buffers 是對原始磁碟塊的臨時儲存,也就是用來快取磁碟的資料,通常不會特別大(20MB 左右)。這樣,核心就可以把分散的寫集中起來,統一優化磁碟的寫入,比如可以把多次小的寫合併成單次大的寫等等。
- Cached 是從磁碟讀取檔案的頁快取,也就是用來快取從檔案讀取的資料。這樣,下次訪問這些檔案資料時,就可以直接從記憶體中快速獲取,而不需要再次訪問緩慢的磁碟。
- 關於磁碟和檔案的區別?
- 磁碟是一個塊裝置,可以劃分為不同的分割槽;在分割槽之上再建立檔案系統,掛載到某個目錄,之後才可以在這個目錄中讀寫檔案。
- 其實 Linux 中“一切皆檔案”,而文章中提到的“檔案”是普通檔案,磁碟是塊裝置檔案,這些大家可以執行 “ls -l <路徑>” 檢視它們的區別
- 在讀寫普通檔案時,會經過檔案系統,由檔案系統負責與磁碟互動;而讀寫磁碟或者分割槽時,就會跳過檔案系統,也就是所謂的“裸I/O“。這兩種讀寫方式所使用的快取是不同的,也就是文中所講的 Cache 和 Buffer 區別。
工具介紹
記憶體的效能指標
從記憶體指標出發,列舉了哪些效能工具可以提供這些指標。這樣,在實際排查效能問題時,你就可以清楚知道,究竟要用什麼工具來輔助分析,提供你想要的指標。
本作品採用《CC 協議》,轉載必須註明作者和本文連結