內存不足:殺死進程還是犧牲子進程
早上6點,我不得不開始處理“叫醒”我的一些問題。因為當這些問題發生的時候,我的手機鈴聲響了。昏睡中的我非常不情愿地拿起了手機,檢查我是否瘋狂到將叫醒鬧鐘設在了早上5點。原來是監控系統發現一個Plumbr服務死掉了。
作為一名該領域經驗豐富的高手,我首先來到了咖啡機旁。我需要用一杯咖啡開始工作。第一個問題,在應用崩潰之前看起來一切運行正常。日志中沒有錯誤,沒有告警,也沒有其他任何異常。
我們的監控系統已經察覺到進程死掉了,并且已經重啟了崩潰的服務。因為血液中已經有了咖啡因,我開始收集更多的證據。30分鐘后,在/var/log/kern.log文件中發現了以下內容:
- Jun 4 07:41:59 plumbr kernel: [70667120.897649] Out of memory: Kill process 29957 (java) score 366 or sacrifice child
- Jun 4 07:41:59 plumbr kernel: [70667120.897701] Killed process 29957 (java) total-vm:2532680kB, anon-rss:1416508kB, file-rss:0kB
很顯然,我們成了Linux內核的受害者。大家都知道,Linux建立在一些守護進程之上。這些守護進程被幾個看起來糟透了的內核任務看管。所有現代Linux內核都內置了一個被稱為“內存不足殺手”的機制,它在內存不足的情況下會殺掉用戶進程。當檢測到內存不足時,殺手會被激活并選擇一個進程殺死。選擇機制是用啟發式算法對所有進程進行打分,最后選擇得分最低的進程殺死。
理解“內存不足殺手”
默認情況下,Linux內核允許進程請求比當前系統可用內存更多的內存。這是有道理的,因為大部分進程從來不會用掉它們請求的所有內存。就像有線網絡運營商,他們承諾每個用戶100Mbit的下載速度,這遠遠超出了運營商網絡的真實帶寬。因為他們認為所有用戶不會同時達到帶寬的上限。所以,一個10Gbit的鏈路能夠很好地為100個用戶提供服務超。
這種機制的一個副作用是,一些程序會消耗系統內存。這將導致內存不足,使得沒有內存頁面可以分配給進程。你可能遇到過這種情況,只有root賬號才能殺掉offending任務。為了避免這種情況發生,殺手進程會被啟動,識別進程并殺死它。
更多關于“內存不足殺手”的內容請參見這篇RedHad的文檔。
內存不足殺手由誰觸發?
現在,我們知道了一些背景知識,但是內存不足殺手由誰觸發?究竟什么原因讓我在早上5點被叫醒?一些調查顯示:
/proc/sys/vm/overcommit_memory中的配置允許過量使用內存,它被設置為1,意味著每一次malloc都能夠成功申請到內存。
應用運行在一個EC2 m1.small實例上。EC2實例默認是不支持交換區的。
這兩點再加上突然增加的訪問導致了我們的應用會申請越來越多的內存以支持這些用戶。過量使用內存配置也允許為這些進程申請越來越多的內存,最后觸發了“內存不足殺手”,就像它的名字那樣,殺死我們的應用然后在半夜把我叫醒。
示例
當我向工程師們描述這個問題時,有一個很有興趣的工程師用一個小測試程序來復現這個問題。當在Linux(最新穩定版Ubuntu)上編譯和加載下面的Java代碼片段時,
- 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();
- }
- }
- }
- }
你會發現類似下面的消息:Kill process (java) score 或犧牲子進程的消息。
注意:你可能需要修改交換區和堆大小。在我的測試程序中,將堆大小通過-Xmx2g設置成2G,通過如下配置設置交換區大小:
- swapoff -a
- dd if=/dev/zero of=swapfile bs=1024 count=655360
- mkswap swapfile
- swapon swapfile
解決方案?
有很多種方法可以解決這個問題。在我們的示例中,我們只是把系統遷移到一個有更大內存的實例中。并且我還建議允許交換,但是當咨詢過工程人員后,我意識到Java虛擬機中的垃圾回收進程在交換時表現不是很好,所以這個選項最后沒有被采用。
其他可能有用的方案包括微調內存不足殺手,在幾個實例間進行負載均衡或者降低應用的內存需求。