天天看點

記憶體滿了,會發生什麼?

1、記憶體配置設定的過程是怎樣的?

應用程式通過 malloc 函數(全稱是memory allocation,中文叫動态記憶體配置設定)申請記憶體的時候,實際上申請的是虛拟記憶體,此時并不會配置設定實體記憶體。

當應用程式讀寫了這塊虛拟記憶體,CPU 就會去通路這個虛拟記憶體, 這時會發現這個虛拟記憶體沒有映射到實體記憶體, CPU 就會産生缺頁中斷,程序會從使用者态切換到核心态,并将缺頁中斷交給核心的 Page Fault Handler (缺頁中斷函數)處理。

缺頁中斷處理函數會看是否有空閑的實體記憶體,如果有,就直接配置設定實體記憶體,并建立虛拟記憶體與實體記憶體之間的映射關系。

如果沒有空閑的實體記憶體,那麼核心就會開始進行回收記憶體的工作,回收的方式主要是兩種:直接記憶體回收和背景記憶體回收。

  • 背景記憶體回收(kswapd):在實體記憶體緊張的時候,會喚醒 kswapd 核心線程來回收記憶體,這個回收記憶體的過程異步的,不會阻塞程序的執行。
  • 直接記憶體回收(direct reclaim):如果背景異步回收跟不上程序記憶體申請的速度,就會開始直接回收,這個回收記憶體的過程是同步的,會阻塞程序的執行。

如果直接記憶體回收後,空閑的實體記憶體仍然無法滿足此次實體記憶體的申請,那麼核心就會放最後的大招了 ——觸發 OOM (Out of Memory)機制。

OOM Killer 機制會根據算法選擇一個占用實體記憶體較高的程序,然後将其殺死,以便釋放記憶體資源,如果實體記憶體依然不足,OOM Killer 會繼續殺死占用實體記憶體較高的程序,直到釋放足夠的記憶體位置。

申請實體記憶體的過程如下圖:

記憶體滿了,會發生什麼?

2、哪些記憶體可以被回收?

系統記憶體緊張的時候,就會進行回收記憶體的工作,那具體哪些記憶體是可以被回收的呢?

主要有兩類記憶體可以被回收,而且它們的回收方式也不同。

  • 檔案頁(File-backed Page):核心緩存的磁盤資料(Buffer)和核心緩存的檔案資料(Cache)都叫作檔案頁。大部分檔案頁,都可以直接釋放記憶體,以後有需要時,再從磁盤重新讀取就可以了。而那些被應用程式修改過,并且暫時還沒寫入磁盤的資料(也就是髒頁),就得先寫入磁盤,然後才能進行記憶體釋放。是以,回收幹淨頁的方式是直接釋放記憶體,回收髒頁的方式是先寫回磁盤後再釋放記憶體。
  • 匿名頁(Anonymous Page):這部分記憶體沒有實際載體,不像檔案緩存有硬碟檔案這樣一個載體,比如堆、棧資料等。這部分記憶體很可能還要再次被通路,是以不能直接釋放記憶體,它們回收的方式是通過 Linux 的 Swap 機制,Swap 會把不常通路的記憶體先寫到磁盤中,然後釋放這些記憶體,給其他更需要的程序使用。再次通路這些記憶體時,重新從磁盤讀入記憶體就可以了。

檔案頁和匿名頁的回收都是基于 LRU 算法,也就是優先回收不常通路的記憶體。LRU 回收算法,實際上維護着 active 和 inactive 兩個雙向連結清單,其中:

  • active_list活躍記憶體頁連結清單,這裡存放的是最近被通路過(活躍)的記憶體頁;
  • inactive_list不活躍記憶體頁連結清單,這裡存放的是很少被通路(非活躍)的記憶體頁;

越接近連結清單尾部,就表示記憶體頁越不常通路。這樣,在回收記憶體時,系統就可以根據活躍程度,優先回收不活躍的記憶體。

活躍和非活躍的記憶體頁,按照類型的不同,又分别分為檔案頁和匿名頁。可以從 /proc/meminfo 中,查詢它們的大小,比如:

# grep表示隻保留包含active的名額(忽略大小寫)
# sort表示按照字母順序排序
[root@xiaolin ~]# cat /proc/meminfo | grep -i active | sort
Active:           901456 kB
Active(anon):     227252 kB
Active(file):     674204 kB
Inactive:         226232 kB
Inactive(anon):    41948 kB
Inactive(file):   184284 kB      

3、回收記憶體帶來的性能影響

在前面我們知道了回收記憶體有兩種方式。

  • 一種是背景記憶體回收,也就是喚醒 kswapd 核心線程,這種方式是異步回收的,不會阻塞程序。
  • 一種是直接記憶體回收,這種方式是同步回收的,會阻塞程序,這樣就會造成很長時間的延遲,以及系統的 CPU 使用率會升高,最終引起系統負荷飙高。

可被回收的記憶體類型有檔案頁和匿名頁:

  • 檔案頁的回收:對于幹淨頁是直接釋放記憶體,這個操作不會影響性能,而對于髒頁會先寫回到磁盤再釋放記憶體,這個操作會發生磁盤 I/O 的,這個操作是會影響系統性能的。
  • 匿名頁的回收:如果開啟了 Swap 機制,那麼 Swap 機制會将不常通路的匿名頁換出到磁盤中,下次通路時,再從磁盤換入到記憶體中,這個操作是會影響系統性能的。

可以看到,回收記憶體的操作基本都會發生磁盤 I/O 的,如果回收記憶體的操作很頻繁,意味着磁盤 I/O 次數會很多,這個過程勢必會影響系統的性能,整個系統給人的感覺就是很卡。

下面針對回收記憶體導緻的性能影響,說說常見的解決方式。

​​​​1)調整檔案頁和匿名頁的回收傾向

從檔案頁和匿名頁的回收操作來看,檔案頁的回收操作對系統的影響相比匿名頁的回收操作會少一點,因為檔案頁對于幹淨頁回收是不會發生磁盤 I/O 的,而匿名頁的 Swap 換入換出這兩個操作都會發生磁盤 I/O。

Linux 提供了一個 ​

​/proc/sys/vm/swappiness​

​ 選項,用來調整檔案頁和匿名頁的回收傾向。

swappiness 的範圍是 0-100,數值越大,越積極使用 Swap,也就是更傾向于回收匿名頁;數值越小,越消極使用 Swap,也就是更傾向于回收檔案頁。

[root@xiaolin ~]# cat /proc/sys/vm/swappiness
0      

一般建議 swappiness 設定為 0(預設值是 60),這樣在回收記憶體的時候,會更傾向于檔案頁的回收,但是并不代表不會回收匿名頁。

​​​​2)盡早觸發 kswapd 核心線程異步回收記憶體

如何檢視系統的直接記憶體回收和背景記憶體回收的名額?

我們可以使用 ​

​sar -B 1​

​ 指令來觀察:

記憶體滿了,會發生什麼?

圖中紅色框住的就是背景記憶體回收和直接記憶體回收的名額,它們分别表示:

  • pgscank/s : kswapd(背景回收線程) 每秒掃描的 page 個數。
  • pgscand/s: 應用程式在記憶體申請過程中每秒直接掃描的 page 個數。
  • pgsteal/s: 掃描的 page 中每秒被回收的個數(pgscank+pgscand)。

如果系統時不時發生抖動,并且在抖動的時間段裡如果通過 sar -B 觀察到 pgscand 數值很大,那大機率是因為「直接記憶體回收」導緻的。

針對這個問題,解決的辦法就是,可以通過盡早的觸發「背景記憶體回收」來避免應用程式進行直接記憶體回收。

什麼條件下才能觸發 kswapd 核心線程回收記憶體呢?

核心定義了三個記憶體門檻值(watermark,也稱為水位),用來衡量目前剩餘記憶體(pages_free)是否充裕或者緊張,分别是:

  • 頁最小門檻值(pages_min);
  • 頁低門檻值(pages_low);
  • 頁高門檻值(pages_high);

這三個記憶體門檻值會劃分為四種記憶體使用情況,如下圖:

記憶體滿了,會發生什麼?

kswapd 會定期掃描記憶體的使用情況,根據剩餘記憶體(pages_free)的情況來進行記憶體回收的工作。

  • 圖中綠色部分:如果剩餘記憶體(pages_free)大于 頁高門檻值(pages_high),說明剩餘記憶體是充足的;
  • 圖中藍色部分:如果剩餘記憶體(pages_free)在頁高門檻值(pages_high)和頁低門檻值(pages_low)之間,說明記憶體有一定壓力,但還可以滿足應用程式申請記憶體的請求;
  • 圖中橙色部分:如果剩餘記憶體(pages_free)在頁低門檻值(pages_low)和頁最小門檻值(pages_min)之間,說明記憶體壓力比較大,剩餘記憶體不多了。這時 kswapd0 會執行記憶體回收,直到剩餘記憶體大于高門檻值(pages_high)為止。雖然會觸發記憶體回收,但是不會阻塞應用程式,因為兩者關系是異步的。
  • 圖中紅色部分:如果剩餘記憶體(pages_free)小于頁最小門檻值(pages_min),說明使用者可用記憶體都耗盡了,此時就會觸發直接記憶體回收,這時應用程式就會被阻塞,因為兩者關系是同步的。

可以看到,當剩餘記憶體頁(pages_free)小于頁低門檻值(pages_low),就會觸發 kswapd 進行背景回收,然後 kswapd 會一直回收到剩餘記憶體頁(pages_free)大于頁高門檻值(pages_high)。

也就是說 kswapd 的活動空間隻有 pages_low 與 pages_min 之間的這段區域,如果剩餘記憶體低于了 pages_min 會觸發直接記憶體回收,高于了 pages_high 又不會喚醒 kswapd。

頁低門檻值(pages_low)可以通過核心選項 ​

​/proc/sys/vm/min_free_kbytes​

​ (該參數代表系統所保留白閑記憶體的最低限)來間接設定。

min_free_kbytes 雖然設定的是頁最小門檻值(pages_min),但是頁高門檻值(pages_high)和頁低門檻值(pages_low)都是根據頁最小門檻值(pages_min)計算生成的,它們之間的計算關系如下:

pages_min = min_free_kbytes
pages_low = pages_min*5/4
pages_high = pages_min*3/2      

繼續閱讀