oom機制分析及對應優化策略

近日接了一些oom案子,此類問題通常是客戶自身業務導致的問題。但現在客戶的提問越來越複雜,通常情況下我們需要站在客戶一側提供“協助”的技術服務。oom類案例通過一年多的學習和探討,我將其分爲3類:

1、內存真的不足

2、文件數到達上限

3、lowmem內存不足

內存真的不足的情況針對於64位系統,這時可能導致的原因有:進程hung住佔用大量內存、進程申請連續大頁導致溢出、本身內存負載接近臨界值。這時可嘗試升級內存配置、優化業務進程來解決。

單個進程最多允許打開的文件句柄數(包括socket連接數)是有限制的,當大於這個系統限制時,程序會拋出大量的無法打開文件的報錯。這種情況設涉及到幾個內核參數:

/proc/sys/fs/nr_open——系統文件系統支持文件句柄總數上限,默認值1048576(1M),Linux2.6.25開始增加該內核參數,用於替換內核宏NR_OPEN(1048576),該值上限受限於系統內存。

/proc/sys/fs/file-max——系統文件系統支持文件句柄總數最大值,必須小於/proc/sys/fs/nr_open或NR_OPEN,增加該值時,必須同步修改/proc/sys/fs/inode-max = 4*/proc/sys/fs/file-max。

因此當出現messages中存在相關句柄數達上限之類的提示時就可以適當調高上述內核參數

對於32位機器會出現一些內存使用率不高卻仍然發生oom的情況,這是因爲對於32位機器內核採用lowmem來管理highmem。LowMem 區 (也叫 NORMAL ZONE ) 一共 880 MB,而且不能改變(除非用 hugemem 內核)。對於高負載的系統,就可能因爲 LowMem 利用不好而引發 OOM Killer 。一個可能原因是 LowFree 太少了,另外一個原因是 LowMem 裏都是碎片,請求不到連續的內存區域。

檢查當前 LowFree 的值:

# cat /proc/meminfo |grep LowFree

檢查LowMem內存碎片:

# cat /proc/buddyinfo

這時可以通過升級到64位系統、使用hugemem內核(安裝hugemem kernel RPM)、適當調高/proc/sys/vm/lower_zone_protection(lower_zone_protection越高,系統越傾向於保護lowmem)來解決。



oom的kill機制



如果oom_kill_allcating_task設置爲非零值,則oom根據score_adj來選擇殺掉的進程。 /proc/[pid]/oom_adj ,該pid進程被oom killer殺掉的權重,介於 [-17,15]之間,越高的權重,意味着更可能被oom killer選中,-17表示禁止被kill掉。關於kill機制,涉及到2個內核參數:

1、oom_kill_allocating_task

控制在OOM時是否殺死觸發OOM的進程。

如果設置爲0,OOM killer會掃描進程列表,選擇一個進程來殺死。通常都會選擇消耗內存內存最多的進程,殺死這樣的進程後可以釋放大量的內存。

如果設置爲非零值,OOM killer只會簡單地將觸發OOM的進程殺死,避免遍歷進程列表(代價比較大)。如果panic_on_oom被設置,則會忽略oom_kill_allocating_task的值。

默認值是0。

2、vm.panic_on_oom

控制內核在OOM發生時時是否panic。

如果設置爲0,內核會殺死內存佔用過多的進程。通常殺死內存佔用最多的進程,系統就會恢復。

如果設置爲1,在發生OOM時,內核會panic。然而,如果一個進程通過內存策略或進程綁定限制了可以使用的節點,並且這些節點的內存已經耗盡,oom-killer可能會殺死一個進程來釋放內存。在這種情況下,內核不會panic,因爲其他節點的內存可能還有空閒,這意味着整個系統的內存狀況還沒有處於崩潰狀態。

如果設置爲2,在發生OOM時總是會強制panic,即使在上面討論的情況下也一樣。即使在memory cgroup限制下發生的OOM,整個系統也會panic。

默認值是0。

將該參數設置爲1或2,通常用於集羣的故障切換。選擇何種方式,取決於你的故障切換策略。

代碼判斷邏輯如下:

void out_of_memory(struct zonelist *zonelist, gfp_t gfp_mask,

int order, nodemask_t *nodemask, bool force_kill)

{

// 等待notifier調用鏈返回,如果有內存了則返回

blocking_notifier_call_chain(&oom_notify_list, 0, &freed);

if (freed > 0)

return;

// 如果進程即將退出,則表明可能會有內存可以使用了,返回

if (fatal_signal_pending(current) || current->flags & PF_EXITING) {

set_thread_flag(TIF_MEMDIE);

return;

}

// 如果設置了sysctl的panic_on_oom,則內核直接panic

check_panic_on_oom(constraint, gfp_mask, order, mpol_mask);

// 如果設置了oom_kill_allocating_task

// 則殺死正在申請內存的process

if (sysctl_oom_kill_allocating_task && current->mm &&

!oom_unkillable_task(current, NULL, nodemask) &&

current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) {

get_task_struct(current);

oom_kill_process(current, gfp_mask, order, 0, totalpages, NULL,

nodemask,

"Out of memory (oom_kill_allocating_task)");

goto out;

}

// 用select_bad_process()選擇badness指

// 數(oom_score)最高的進程

p = select_bad_process(&points, totalpages, mpol_mask, force_kill);

if (!p) {

dump_header(NULL, gfp_mask, order, NULL, mpol_mask);

panic("Out of memory and no killable processes...\n");

}

if (p != (void *)-1UL) {

// 查看child process, 是否是要被killed,則直接影響當前這個parent進程

oom_kill_process(p, gfp_mask, order, points, totalpages, NULL,

nodemask, "Out of memory");

killed = 1;

}

out:

if (killed)

schedule_timeout_killable(1);

}

計算權值時會將是否爲系統進程、進程RSS和swap內存佔用考慮進去。當然也可自行設置:echo 17 > /proc/[pid]/oom_adj(不允許oom殺掉這個進程)

計算邏輯代碼如下:

unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,

const nodemask_t *nodemask, unsigned long totalpages)

{

long points;

long adj;

// 內部判斷是否是pid爲1的initd進程,是否是kthread內核進程,是否是其他cgroup,如果是則跳過

if (oom_unkillable_task(p, memcg, nodemask))

return 0;

p = find_lock_task_mm(p);

if (!p)

return 0;

// 獲得/proc/[pid]/oom_adj權值,如果是OOM_SCORE_ADJ_MIN則返回

adj = (long)p->signal->oom_score_adj;

if (adj == OOM_SCORE_ADJ_MIN) {

task_unlock(p);

return 0;

}

// 獲得進程RSS和swap內存佔用

points = get_mm_rss(p->mm) + p->mm->nr_ptes +

get_mm_counter(p->mm, MM_SWAPENTS);

task_unlock(p);

// 計算步驟如下,【計算邏輯比較簡單,不贅述了】

if (has_capability_noaudit(p, CAP_SYS_ADMIN))

adj -= 30;

adj *= totalpages / 1000;

points += adj;

return points > 0 ? points : 1;

}




相關內核參數解析



dirty_background_bytes

當髒頁所佔的內存數量超過dirty_background_bytes時,內核的pdflush線程開始回寫髒頁。

dirty_background_ratio

默認值 :10

參數意義:當髒頁所佔的百分比(相對於所有可用內存,即空閒內存頁+可回收內存頁)達到dirty_background_ratio時內核的pdflush線程開始回寫髒頁。增大會使用更多內存用於緩衝,可以提高系統的讀寫性能。當需要持續、恆定的寫入場合時,應該降低該數值。

注意:dirty_background_bytes參數和dirty_background_ratio參數是相對的,只能指定其中一個。當其中一個參數文件被寫入時,會立即開始計算髒頁限制,並且會將另一個參數的值清零。

dirty_bytes

當髒頁所佔的內存數量達到dirty_bytes時,執行磁盤寫操作的進程自己開始回寫髒數據。

  注意:dirty_bytes參數和dirty_ratio參數是相對的,只能指定其中一個。當其中一個參數文件被寫入時,會立即開始計算髒頁限制,並且會將另一個參數的值清零

dirty_ratio

默認值:40

參數意義:當髒頁所佔的百分比(相對於所有可用內存,即空閒內存頁+可回收內存頁)達到dirty_ratio時,進程pdflush會自己開始回寫髒數據。增大會使用更多系統內存用於緩衝,可以提高系統的讀寫性能。當需要持續、恆定的寫入場合時,應該降低該數值。

dirty_expire_centisecs

默認值:2999參數意義:用來指定內存中數據是多長時間纔算髒(dirty)數據。指定的值是按100算做一秒計算。只有當超過這個值後,纔會觸發內核進程pdflush將dirty數據寫到磁盤。

dirty_writeback_centisecs

默認值:499這個參數會觸發pdflush回寫進程定期喚醒並將old數據寫到磁盤。每次的喚醒的間隔,是以數字100算做1秒。如果將這項值設爲500就相當5秒喚醒pdflush進程。如果將這項值設爲0就表示完全禁止定期回寫數據。

drop_caches

向/proc/sys/vm/drop_caches文件中寫入數值可以使內核釋放page cache,dentries和inodes緩存所佔的內存。

  只釋放pagecache:

      echo 1 > /proc/sys/vm/drop_caches

  只釋放dentries和inodes緩存:

      echo 2 > /proc/sys/vm/drop_caches

  釋放pagecache、dentries和inodes緩存:

      echo 3 > /proc/sys/vm/drop_caches

  這個操作不是破壞性操作,髒的對象(比如髒頁)不會被釋放,因此要首先運行sync命令。

注:這個只能是手動釋放

legacy_va_layout

該文件表示是否使用最新的32位共享內存mmap()系統調用,Linux支持的共享內存分配方式包括mmap(),Posix,System VIPC。

0,使用最新32位mmap()系統調用。

1,使用2.4內核提供的系統調用。

lowmem_reserve_ratio

保留的lowmem,3列分別爲DMA/normal/HighMem

在有高端內存的機器上,從低端內存域給應用層進程分配內存是很危險的,因爲這些內存可以通過mlock()系統鎖定,或者變成不可用的swap空間。在有大量高端內存的機器上,缺少可以回收的低端內存是致命的。因此如果可以使用高端內存,Linux頁面分配器不會使用低端內存。這意味着,內核會保護一定數量的低端內存,避免被用戶空間鎖定。

  這個參數同樣可以適用於16M的ISA DMA區域,如果可以使用低端或高端內存,則不會使用該區域。

  lowmem_reserve_ratio參數決定了內核保護這些低端內存域的強度。預留的內存值和lowmem_reserve_ratio數組中的值是倒數關係,如果值是256,則代表1/256,即爲0.39%的zone內存大小。如果想要預留更多頁,應該設更小一點的值。

min_free_kbytes

這個參數用來指定強制Linux VM保留的內存區域的最小值,單位是kb。VM會使用這個參數的值來計算系統中每個低端內存域的watermark[WMARK_MIN]值。每個低端內存域都會根據這個參數保留一定數量的空閒內存頁。

  一部分少量的內存用來滿足PF_MEMALLOC類型的內存分配請求。如果進程設置了PF_MEMALLOC標誌,表示不能讓這個進程分配內存失敗,可以分配保留的內存。並不是所有進程都有的。kswapd、direct reclaim的process等在回收的時候會設置這個標誌,因爲回收的時候它們還要爲自己分配一些內存。有了PF_MEMALLOC標誌,它們就可以獲得保留的低端內存。

  如果設置的值小於1024KB,系統很容易崩潰,在負載較高時很容易死鎖。如果設置的值太大,系統會經常OOM。

max_map_count

進程中內存映射區域的最大數量。在調用malloc,直接調用mmap和mprotect和加載共享庫時會產生內存映射區域。雖然大多數程序需要的內存映射區域不超過1000個,但是特定的程序,特別是malloc調試器,可能需要很多,例如每次分配都會產生一到兩個內存映射區域。默認值是65536。

mmap_min_addr

         指定用戶進程通過mmap可使用的最小虛擬內存地址,以避免其在低地址空間產生映射導致安全問題;如果非0,則不允許mmap到NULL頁,而此功能可在出現NULL指針時調試Kernel;mmap用於將文件映射至內存;該設置意味着禁止用戶進程訪問low 4k地址空間

nr_pdflush_threads

當前pdfflush線程數量,爲read-only。

oom_dump_tasks

如果啓用,在內核執行OOM-killing時會打印系統內進程的信息(不包括內核線程),信息包括pid、uid、tgid、vm size、rss、nr_ptes,swapents,oom_score_adj和進程名稱。這些信息可以幫助找出爲什麼OOM killer被執行,找到導致OOM的進程,以及瞭解爲什麼進程會被選中。

  如果將參數置爲0,不會打印系統內進程的信息。對於有數千個進程的大型系統來說,打印每個進程的內存狀態信息並不可行。這些信息可能並不需要,因此不應該在OOM的情況下犧牲性能來打印這些信息。

  如果設置爲非零值,任何時候只要發生OOM killer,都會打印系統內進程的信息。

  默認值是1(啓用)。

OOM killer(Out-Of-Memory killer):監控那些佔用內存過大,尤其是瞬間很快消耗大量內存的進程,爲了防止內存耗盡而內核會把該進程殺掉

oom_kill_allocating_task

控制在OOM時是否殺死觸發OOM的進程。

    如果設置爲0,OOM killer會掃描進程列表,選擇一個進程來殺死。通常都會選擇消耗內存內存最多的進程,殺死這樣的進程後可以釋放大量的內存。

    如果設置爲非零值,OOM killer只會簡單地將觸發OOM的進程殺死,避免遍歷進程列表(代價比較大)。如果panic_on_oom被設置,則會忽略oom_kill_allocating_task的值。

        默認值是0。

panic_on_oom

控制內核在OOM發生時時是否panic。

  如果設置爲0,內核會殺死內存佔用過多的進程。通常殺死內存佔用最多的進程,系統就會恢復。

  如果設置爲1,在發生OOM時,內核會panic。然而,如果一個進程通過內存策略或進程綁定限制了可以使用的節點,並且這些節點的內存已經耗盡,oom-killer可能會殺死一個進程來釋放內存。在這種情況下,內核不會panic,因爲其他節點的內存可能還有空閒,這意味着整個系統的內存狀況還沒有處於崩潰狀態。

  如果設置爲2,在發生OOM時總是會強制panic,即使在上面討論的情況下也一樣。即使在memory cgroup限制下發生的OOM,整個系統也會panic。

  默認值是0。

  將該參數設置爲1或2,通常用於集羣的故障切換。選擇何種方式,取決於你的故障切換策略。

overcommit_memory

默認值爲:0從內核文檔裏得知,該參數有三個值,分別是:0:當用戶空間請求更多的的內存時,內核嘗試估算出剩餘可用的內存。

1:當設這個參數值爲1時,內核允許超量使用內存直到用完爲止,主要用於科學計算

2:當設這個參數值爲2時,內核會使用一個決不過量使用內存的算法,即系統整個內存地址空間不能超過swap+50%的RAM值,50%參數的設定是在overcommit_ratio中設定。

overcommit_ratio

默認值爲:50這個參數值只有在vm.overcommit_memory=2的情況下,這個參數纔會生效。該值爲物理內存比率,當overcommit_memory=2時,進程可使用的swap空間不可超過PM * overcommit_ratio/100

Swappiness

該參數控制是否使用swap分區,以及使用的比例。設置的值越大,內核會越傾向於使用swap。如果設置爲0,內核只有在看空閒的和基於文件的內存頁數量小於內存域的高水位線(應該指的是watermark[high])時纔開始swap。

  默認值是60。

vfs_cache_pressure

控制內核回收dentry和inode cache內存的傾向。

  默認值是100,內核會根據pagecache和swapcache的回收情況,讓dentry和inode cache的內存佔用量保持在一個相對公平的百分比上。

  減小vfs_cache_pressure會讓內核更傾向於保留dentry和inode cache。當vfs_cache_pressure等於0,在內存緊張時,內核也不會回收dentry和inode cache,這容易導致OOM。如果vfs_cache_pressure的值超過100,內核會更傾向於回收dentry和inode cache。

 

優化的建議



echo 【調高】(針對32位) > /proc/sys/vm/lower_zone_protection

echo 【調低】 > /proc/sys/vm/min_free_kbytes

echo 【調高】 > /proc/sys/vm/dirty_ratio

echo 【調低】 > /proc/sys/vm/dirty_background_ratio

echo 【調低】> /proc/sys/vm/lowmem_reserve_ratio

echo 【調高】> /proc/sys/vm/swappiness

echo 【調高】> /proc/sys/fs/file-max

echo 2 > /proc/sys/vm/overcommit_memory

調高/etc/security/limits.conf連接數目

當然也可通過直接關閉oom規避,但是這樣很危險,可能會導致系統發生crash

關閉/打開oom-killer:

# echo "0" > /proc/sys/vm/oom-kill

# echo "1" > /proc/sys/vm/oom-kill


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章