Linux 內(nèi)核有個(gè)機(jī)制叫OOM killer(Out-Of-Memory killer),該機(jī)制會(huì)監(jiān)控那些占用內(nèi)存過大,尤其是瞬間很快消耗大量內(nèi)存的進(jìn)程,為了防止內(nèi)存耗盡而內(nèi)核會(huì)把該進(jìn)程殺掉。典型的情況是:某天一臺(tái)機(jī)器突然ssh遠(yuǎn)程登錄不了,但能ping通,說明不是網(wǎng)絡(luò)的故障,原因是sshd進(jìn)程被OOM killer殺掉了(多次遇到這樣的假死狀況)。重啟機(jī)器后查看系統(tǒng)日志/var/log/messages會(huì)發(fā)現(xiàn)Out of Memory: Kill process 1865(sshd)類似的錯(cuò)誤信息。
防止重要的系統(tǒng)進(jìn)程觸發(fā)(OOM)機(jī)制而被殺死:可以設(shè)置參數(shù)/proc/PID/oom_adj為-17,可臨時(shí)關(guān)閉linux內(nèi)核的OOM機(jī)制。內(nèi)核會(huì)通過特定的算法給每個(gè)進(jìn)程計(jì)算一個(gè)分?jǐn)?shù)來決定殺哪個(gè)進(jìn)程,每個(gè)進(jìn)程的oom分?jǐn)?shù)可以/proc/PID/oom_score中找到。我們運(yùn)維過程中保護(hù)的一般是sshd和一些管理agent。
保護(hù)某個(gè)進(jìn)程不被內(nèi)核殺掉可以這樣操作:
echo -17 》 /proc/$PID/oom_adj
如何防止sshd被殺,可以這樣操作:
pgrep -f “/usr/sbin/sshd” | while read PID;do echo -17 》 /proc/$PID/oom_adj;done
可以在計(jì)劃任務(wù)里加入這樣一條定時(shí)任務(wù),就更安全了:
#/etc/cron.d/oom_disable
*/1**** root pgrep -f “/usr/sbin/sshd” | while read PID;do echo -17 》 /proc/$PID/oom_adj;done
為了避免重啟失效,可以寫入/etc/rc.d/rc.local
echo -17 》 /proc/$(pidof sshd)/oom_adj
至于為什么用-17而不用其他數(shù)值(默認(rèn)值為0),這個(gè)是由linux內(nèi)核定義的,查看內(nèi)核源碼可知:
以linux-3.3.6版本的kernel源碼為例,路徑為linux-3.6.6/include/linux/oom.h,閱讀內(nèi)核源碼可知oom_adj的可調(diào)值為15到-16,其中15最大-16最小,-17為禁止使用OOM。oom_score為2的n次方計(jì)算出來的,其中n就是進(jìn)程的oom_adj值,所以oom_score的分?jǐn)?shù)越高就越會(huì)被內(nèi)核優(yōu)先殺掉。
當(dāng)然還可以通過修改內(nèi)核參數(shù)禁止OOM機(jī)制
# sysctl -w vm.panic_on_oom=1
vm.panic_on_oom = 1 //1表示關(guān)閉,默認(rèn)為0表示開啟OOM
# sysctl -p
為了驗(yàn)證OOM機(jī)制的效果,我們不妨做個(gè)測試。
首先看看我系統(tǒng)現(xiàn)有內(nèi)存大小,沒錯(cuò)96G多,物理上還要比查看的值大一些。
再看看目前進(jìn)程最大的有哪些,top查看,我目前只跑了兩個(gè)java程序的進(jìn)程,分別4.6G,再往后redis進(jìn)程吃了21m,iscsi服務(wù)占了32m,gdm占了25m,其它的進(jìn)程都是幾M而已。
現(xiàn)在我自己用C寫一個(gè)叫bigmem程序,我指定該程序分配內(nèi)存85G
#include 《stdio.h》
#include 《stdlib.h》
#include 《string.h》
#define PAGE_SZ (1《《12)
int main() {
int i;
int gb = 85; //以GB為單位分配內(nèi)存大小
for (i = 0; i 《 ((unsigned long)gb《《30)/PAGE_SZ ; ++i) {
void *m = malloc(PAGE_SZ);
if (!m)
break;
memset(m, 0, 1);
}
printf(“allocated %lu MB\n”, ((unsigned long)i*PAGE_SZ)》》20);
getchar();
return 0;
}
效果明顯,然后執(zhí)行后再用top查看,排在第一位的是我的bigmem,RES是物理內(nèi)存,已經(jīng)吃滿了85G。
繼續(xù)觀察,當(dāng)bigmem穩(wěn)定保持在85G一會(huì)后,內(nèi)核會(huì)自動(dòng)將其進(jìn)程kill掉,增長的過程中沒有被殺,如果不希望被殺可以執(zhí)行
點(diǎn)擊(此處)折疊或打開pgrep -f “bigmem” | while read PID; do echo -17 》 /proc/$PID/oom_adj;done
執(zhí)行以上命令前后,明顯會(huì)對比出效果,就可以體會(huì)到內(nèi)核OOM機(jī)制的實(shí)際作用了。
如果你覺得寫C代碼麻煩,我告訴大家另外一個(gè)最簡單的測試觸發(fā)OOM的方法,可以把某個(gè)進(jìn)程的oom_adj設(shè)置到15(最大值),最容易觸發(fā)。然后執(zhí)行以下命令:
echo f 》 /proc/sysrq-trigger // ‘f’ - Will call oom_kill to kill a memory hog process.
以下我來觸發(fā)mysqld的OOM看看:
需要注意的是這個(gè)測試,只是模擬OOM,不會(huì)真正殺掉進(jìn)程
ps -ef | grep mysqld | grep -v grep
查看mysql進(jìn)程,發(fā)現(xiàn)依然存在
注意:
1.Kernel-2.6.26之前版本的oomkiller算法不夠精確,RHEL 6.x版本的2.6.32可以解決這個(gè)問題。
2.子進(jìn)程會(huì)繼承父進(jìn)程的oom_adj。
3.OOM不適合于解決內(nèi)存泄漏(Memory leak)的問題。
4.有時(shí)free查看還有充足的內(nèi)存,但還是會(huì)觸發(fā)OOM,是因?yàn)樵撨M(jìn)程可能占用了特殊的內(nèi)存地址空間。
評(píng)論
查看更多