系統內存不足是處理機制
內存不足時這其實會導致兩種可能結果,內存回收和 OOM 殺死進程
先來看后一個可能結果,內存資源緊張導致的 OOM(Out Of Memory),相對容易理解,指的是系統殺死占用大量內存的進程,釋放這些內存,再分配給其他更需要的進程。
內存回收機制
大部分文件頁,都可以直接回收,以后有需要時,再從磁盤重新讀取就可以了。而那些被應用程序修改過,並且暫時還沒寫入磁盤的數據(也就是臟頁),就得先寫入磁盤,然后才能進行內存釋放。這些臟頁,一般可以通過兩種方式寫入磁盤。
可以在應用程序中,通過系統調用 fsync ,把臟頁同步到磁盤中;
也可以交給系統,由內核線程 pdflush 負責這些臟頁的刷新。
除了緩存和緩沖區,通過內存映射獲取的文件映射頁,也是一種常見的文件頁。它也可以被釋放掉,下次再訪問的時候,從文件重新讀取。
如,應用程序動態分配的堆內存,也就是內存管理中說到的匿名頁(Anonymous Page);本不應該被回收,因為它們很可能還要再次被訪問啊,當然不能直接回收了;但是,如果這些內存在分配后很少被訪問,似乎也是一種資源浪費。
其實,這正是 Linux 的 Swap 機制。Swap 把這些不常訪問的內存先寫到磁盤中,然后釋放這些內存,給其他更需要的進程使用。再次訪問這些內存時,重新從磁盤讀入內存就可以了。
Swap 原理
Swap 說白了就是把一塊磁盤空間或者一個本地文件,當成內存來使用。它包括換出和換入兩個過程。
換出,就是把進程暫時不用的內存數據存儲到磁盤中,並釋放這些數據占用的內存。
換入,則是在進程再次訪問這些內存的時候,把它們從磁盤讀到內存中來。
Swap 其實是把系統的可用內存變大了。這樣,即使服務器的內存不足,也可以運行大內存的應用程序。
當然,現在的內存便宜多了,服務器一般也會配置很大的內存,也並不是說swap機制就用不到了,事實上,內存再大,對應用程序來說,也有不夠用的時候。一個很典型的場景就是,即使內存不足時,有些應用程序也並不想被 OOM 殺死,而是希望能緩一段時間,等待人工介入,或者等系統自動釋放其他進程的內存,再分配給它。
除此之外,常見的筆記本電腦的休眠和快速開機的功能,也基於 Swap 。休眠時,把系統的內存存入磁盤,這樣等到再次開機時,只要從磁盤中加載內存就可以。這樣就省去了很多應用程序的初始化過程,加快了開機速度。
回收內存的時間
一個最容易想到的場景就是,有新的大塊內存分配請求,但是剩余內存不足。這個時候系統就需要回收一部分內存(比如前面提到的緩存),進而盡可能地滿足新內存請求。這個過程通常被稱為直接內存回收。
除了直接內存回收,還有一個專門的內核線程用來定期回收內存,也就是 kswapd0。為了衡量內存的使用情況,kswapd0 定義了三個內存閾值(watermark,也稱為水位),分別是頁最小閾值(pages_min)、頁低閾值(pages_low)和頁高閾值(pages_high)。剩余內存,則使用 pages_free 表示
kswapd0 定期掃描內存的使用情況,並根據剩余內存落在這三個閾值的空間位置,進行內存的回收操作。
剩余內存小於頁最小閾值,說明進程可用內存都耗盡了,只有內核才可以分配內存。
剩余內存落在頁最小閾值和頁低閾值中間,說明內存壓力比較大,剩余內存不多了。這時 kswapd0 會執行內存回收,直到剩余內存大於高閾值為止。
剩余內存落在頁低閾值和頁高閾值中間,說明內存有一定壓力,但還可以滿足新內存請求。
剩余內存大於頁高閾值,說明剩余內存比較多,沒有內存壓力。
可以看到,一旦剩余內存小於頁低閾值,就會觸發內存的回收。這個頁低閾值,其實可以通過內核選項 /proc/sys/vm/min_free_kbytes 來間接設置。min_free_kbytes 設置了頁最小閾值,而其他兩個閾值,都是根據頁最小閾值計算生成的,計算方法如下
pages_low = pages_min*5/4 pages_high = pages_min*3/2
NUMA 與 Swap的關系
很多情況下,明明發現了 Swap 升高,可是在分析系統的內存使用時,卻很可能發現,系統剩余內存還多着呢。
這正是處理器的 NUMA (Non-Uniform Memory Access)架構導致的。
在 NUMA 架構下,多個處理器被划分到不同 Node 上,且每個 Node 都擁有自己的本地內存空間
而同一個 Node 內部的內存空間,實際上又可以進一步分為不同的內存域(Zone),比如直接內存訪問區(DMA)、普通內存區(NORMAL)、偽內存區(MOVABLE)等,如下圖所示:
先不用特別關注這些內存域的具體含義,只要會查看閾值的配置,以及緩存、匿名頁的實際使用情況就夠了。
既然 NUMA 架構下的每個 Node 都有自己的本地內存空間,那么,在分析內存的使用時,也應該針對每個 Node 單獨分析。
可以通過 numactl 命令,來查看處理器在 Node 的分布情況,以及每個 Node 的內存使用情況。比如,下面就是一個 numactl 輸出的示例:
[root@localhost ~]# numactl --hardware available: 1 nodes (0) node 0 cpus: 0 1 node 0 size: 7966 MB node 0 free: 7427 MB node distances: node 0 0: 10
這個界面顯示,系統中只有一個 Node,也就是 Node 0 ,而且編號為 0 和 1 的兩個 CPU, 都位於 Node 0 上。另外,Node 0 的內存大小為 7966 MB,剩余內存為 7427 MB。
提到的三個內存閾值(頁最小閾值、頁低閾值和頁高閾值),都可以通過內存域在 proc 文件系統中的接口 /proc/zoneinfo 來查看。
$ cat /proc/zoneinfo ... Node 0, zone Normal pages free 227894 min 14896 low 18620 high 22344 ... nr_free_pages 227894 nr_zone_inactive_anon 11082 nr_zone_active_anon 14024 nr_zone_inactive_file 539024 nr_zone_active_file 923986 ...
這個輸出中有大量指標,解釋一下比較重要的幾個。
pages 處的 min、low、high,就是上面提到的三個內存閾值,而 free 是剩余內存頁數,它跟后面的 nr_free_pages 相同。
nr_zone_active_anon 和 nr_zone_inactive_anon,分別是活躍和非活躍的匿名頁數。
nr_zone_active_file 和 nr_zone_inactive_file,分別是活躍和非活躍的文件頁數。
從這個輸出結果可以發現,剩余內存遠大於頁高閾值,所以此時的 kswapd0 不會回收內存。
當然,某個 Node 內存不足時,系統可以從其他 Node 尋找空閑內存,也可以從本地內存中回收內存。具體選哪種模式,你可以通過 /proc/sys/vm/zone_reclaim_mode 來調整。它支持以下幾個選項:
默認的 0 ,也就是剛剛提到的模式,表示既可以從其他 Node 尋找空閑內存,也可以從本地回收內存。
1、2、4 都表示只回收本地內存,2 表示可以回寫臟數據回收內存,4 表示可以用 Swap 方式回收內存。
swappines回收內存的方式
就可以理解內存回收的機制了。這些回收的內存既包括了文件頁,又包括了匿名頁。
對文件頁的回收,當然就是直接回收緩存,或者把臟頁寫回磁盤后再回收。
而對匿名頁的回收,其實就是通過 Swap 機制,把它們寫入磁盤后再釋放內存。
Linux 提供了一個 /proc/sys/vm/swappiness 選項,用來調整使用 Swap 的積極程度。
swappiness 的范圍是 0-100,數值越大,越積極使用 Swap,也就是更傾向於回收匿名頁;數值越小,越消極使用 Swap,也就是更傾向於回收文件頁。雖然 swappiness 的范圍是 0-100,不過要注意,這並不是內存的百分比,而是調整 Swap 積極程度的權重,即使你把它設置成 0,當剩余內存 + 文件頁小於頁高閾值時,還是會發生 Swap。