Linux 的 OOM 終結者(Out Of Memory killer)

 

如今是早晨6點鐘。已經醒來的我正在總結究竟是什麼事情使得個人起牀鬧鈴提早了這麼多。故事剛開始的時候,手機鈴聲剛好中止。又困又煩躁的我看了下手機,看看是否是我本身瘋了把鬧鐘調得這麼早,竟然是早晨5點。然而不是,而是咱們的監控系統顯示,Plumbr服務出故障了。html

做爲這個領域的經驗豐富的老鳥,我打開了咖啡機,這是正確解決問題的第一步。一杯咖啡在手以後,如今我能夠開始處理故障了。首先要懷疑的是應用程序自己,由於它在崩潰以前一點異常也沒有。應用程序日誌中沒有錯誤,沒有警告,也沒有任何可疑的信息。java

咱們部署的監控系統發現進程已經掛掉了並重啓了服務。因爲如今咖啡因已經流淌在個人血液中了,我開始變得信心十足。果真在30分鐘後,我在/var/log/kern.log日誌中發現了下面的信息:linux

  1. Jun 4 07:41:59 plumbr kernel: [70667120.897649] Out of memory: Kill process 29957 (java) score 366 or sacrifice child
  2. Jun 4 07:41:59 plumbr kernel: [70667120.897701] Killed process 29957 (java) total-vm:2532680kB, anon-rss:1416508kB, filers:0kB

很明顯咱們被Linux內核給坑了。你知道的,Linux裏面有許多邪惡的怪物(也叫做守護進程)。這些守護進程是由幾個內核做業所看管的,其中的一個猶爲惡毒。全部的現代Linux內核中都會有一個內存不足終結者(Out of memory Killer, OOM Killer)的內建機制,在內存太低的狀況下,它會殺掉你的進程。當探測到這一狀況時,這個終結者會被激活,而後挑選出一個進程去終結掉。選擇目標進程使用的是一套啓發式算法,它會計算全部進程的分數,而後選出那個分數最低的進程。算法

理解」Out of memory killer「

默認狀況下,Linux內核會容許進程請求的內存超出實際可用內存的大小。這在現實世界中是有意義的,由於大多數進程其實並不會用到全部分配給它的內存(注:同一時間內不會全用到)。和這個問題最相似的就是運營商了。他們承諾賣給用戶的都是100Mb的帶寬,這實際上遠遠超出了他們的網絡容量。他們賭的就是用戶實際上並不會同時用完分配給他們的下載上限。一個10Gb的鏈接能夠很輕鬆地承載100個以上的用戶,這裏的100是經過簡單的數學運算得出的(10G/100M)。網絡

這個作法的一個很明顯的反作用就是,萬一有一個程序正走上了一條耗盡內存的不歸路怎麼辦。這會致使低可用內存的狀況,也就是沒有內存頁可以再分配給進程了。你可能也碰到過這種狀況,沒有root賬戶你是殺不掉這種頑固的進程的。爲了解決這一狀況,終結者被激活了,並找出了要終結的進程。ide

關於"Out of memory killer"參數的調整,能夠參考下這篇文章測試

是誰觸發了Out of memory killer?

雖然如今已經知道發生了什麼,但仍是搞不清楚究竟是誰觸發了這個終結者,而後在早晨5點鐘把我吵醒。進一步的分析後找到了答案:ui

  • /proc/sys/vm/overcommit_memory中的配置容許內存的超量使用——該值設置爲1,這意味着每一個malloc()請求都會成功。
  • 應用程序運行在一臺EC2 m1.small的實例上。EC2的實例默認是禁用了交換分區的。

這兩個因素正好又遇上了咱們服務的忽然的流量高峯,最終致使應用程序爲了支持這些額外的用戶而不斷請求更多的內存。內存超量使用的配置容許這個貪心的進程不停地申請內存,最後會觸發這個內存不足的終結者,它就是來履行它的使命的。去殺掉了咱們的程序,而後在大半夜把我給叫醒。spa

示例

當我把這個狀況描述給工程師的時候,有一位工程師以爲頗有意思,所以寫了個小的測試用例來重現了這個問題。你能夠在Linux下編譯並運行下面這個代碼片斷(我是在最新的穩定版Ubuntu上運行的)。日誌

package eu.plumbr.demo;

public class OOM {

        public static void main(String[] args){

                  java.util.List l = new java.util.ArrayList();

                  for (int i = 10000; i < 100000; i++) {

                          try {

                                  l.add(new int[100_000_000]);

                        } catch (Throwable t) {

                                  t.printStackTrace();

                        }

                }

        }

}

 

而後你就會發現一樣的一個 Out of memory: Kill process (java) score or sacrifice child信息。

注意的是,你可能得調整下交換分區以及堆的大小,在我這個測試用例中,我經過-Xm2g設置了2G大小的堆,同時交換內存使用的是以下的配置:

swapoff -a dd if=/dev/zero of=swapfile bs=1024 count=655360 mkswap swapfile swapon swapfile

 

解決方案?

這種狀況有好幾種解決方案。在咱們這個例子中,咱們只是把系統遷移到了一臺內存更大的機器上(褲子都脫了就讓我看這個?)我也考慮過激活交換分區,不過諮詢了工程師以後我想起來JVM上的GC進程在交換分區下的表現並非很理想,所以這個選項就做罷了。

還有別的一些方法好比OOM killer的調優,或者將負載水平分佈到數個小的實例上,又或者減小應用程序的內存佔用量。

 

參考:

Linux調優:https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/performance_tuning_guide/s-memory-captun

Linux設置swap: https://www.cnblogs.com/kerrycode/p/5246383.html

相關文章
相關標籤/搜索