- 問題現象:
(1) 設備接入BBC(集中管理平台,會占用很大的虛擬內存空間)用top查看到系統free還有100多MB,此時啟動golang程序會出現 out of memory.
(2) 設備不接入BBC,用top查看到系統free還有100多MB,此時啟動golang程序成功(啟動后的golang會占用10MB的物理內存)
疑問:
為什么free值差不多,並且剩余的值遠大於golang程序啟動需要的值,還會出現out of memory呢?
猜測:
(1) 是否是系統要預留了一部分內存,啟動BBC之后再啟動golang程序會導致剩余內存小於系統預留值,而導致分配內存失敗呢?
驗證:
用C語言寫一個需要申請10MB物理內存的程序,看是否能否在設備接入BBC之后再啟動。
結果:
能夠啟動。說明golang程序啟動不了跟申請的物理內存大小無關。
(2) golang程序啟動時會占用超大虛擬內存空間,是否跟系統的虛擬內存分配策略有關?
驗證:系統虛擬內存分配策略跟overcommit_memory有關,原先設備的值為0,限制修改overcommit_memory的值,把值從0改為1.
結果:
設備接入BBC之后還能啟動golang程序。說明golang程序啟動不了跟設備系統的虛擬內存分配策略有關。啟動BBC后再啟動golang程序觸發了CommitLimit,導致內存分配失敗。
分析:
關於overcommit_memory說明:
取值為0,表示內核將檢查是否有足夠的可用內存供應用進程使用;如果有足夠的可用內存,內存申請允許;否則,內存申請失敗,並把錯誤返回給應用進程。系統在為應用程序分配虛擬地址空間時,會判斷當前申請的虛擬地址空間大小是否超過剩余內存大小。如果超過,則虛擬地址空間分配失敗。因此,也就是如果進程本身占用的虛擬地址空間比較大或者剩余內存比較小時,fork、malloc等調用可能會失敗。
取值為1,表示內核允許分配所有的物理內存,而不管當前的內存狀態如何。系統在為應用程序分配虛擬空間時,完全不進行限制,這種情況下,避免了fork可能產生的失敗,但由於malloc是先分配虛擬地址空間,在內存不足的情況下,這相當於完全屏蔽了應用程序對系統內存狀態的感知,即malloc總是能成功,一旦內存不足,會引起系統OOM殺進程,應用程序對於這種后果是無法預測的。
取值為2,表示內核允許分配超過所有物理內存和交換空間總和的內存。是根據系統內存狀態確定了虛擬內存地址空間的上限,由於很多情況下,進程的虛擬地址空間占用遠大於其實際占用的物理內存,這樣一旦內存使用量上去以后,對於一些動態產生的進程(需要復制父進程地址空間)則很容易創建失敗,如果業務過程沒有過多的這種動態申請內存或者創建子進程,則影響不大,否在會產生比較大的影響。
內核相應代碼: int __vm_enough_memory(struct mm_struct *mm, long pages, int cap_sys_admin) { unsigned long free, allowed; vm_acct_memory(pages); /* * Sometimes we want to use more memory than we have */ if (sysctl_overcommit_memory == OVERCOMMIT_ALWAYS) //overcommit_memory=1,直接返回成功,不做任何限制。 return 0; if (sysctl_overcommit_memory == OVERCOMMIT_GUESS) { //overcommit_memory=0,啟發式方式,根據當前系統中空閑內存狀況來決定是否可以分配內存。 unsigned long n; free = global_page_state(NR_FILE_PAGES); free += nr_swap_pages; /* * Any slabs which are created with the * SLAB_RECLAIM_ACCOUNT flag claim to have contents * which are reclaimable, under pressure. The dentry * cache and most inode caches should fall into this */ free += global_page_state(NR_SLAB_RECLAIMABLE); /* * Leave the last 3% for root */ if (!cap_sys_admin) free -= free / 32; //root用戶可以在free更少(3%)的時候,分配內存。 if (free > pages) // pages為需要分配的內存大小,free為根據一定規則算出來的“空閑內存大小”,第一次free僅為NR_FILE_PAGES+NR_SLAB_RECLAIMABLE,由於直接或者系統中“實際空閑”內存代價比較大,所以進行分階判斷,提高效率。 return 0; /* * nr_free_pages() is very expensive on large systems, * only call if we're about to fail. */ n = nr_free_pages(); //當第一次判斷不滿足內存分配條件時,再進行“實際空閑”內存的獲取操作。 /* * Leave reserved pages. The pages are not for anonymous pages. */ if (n <= totalreserve_pages) goto error; else n -= totalreserve_pages; /* * Leave the last 3% for root */ if (!cap_sys_admin) n -= n / 32; free += n; if (free > pages) return 0; goto error; } allowed = (totalram_pages - hugetlb_total_pages()) //當overcommit_memory=2時,根據系統中虛擬地址空間的總量來進行限制。 * sysctl_overcommit_ratio / 100; /* * Leave the last 3% for root */ if (!cap_sys_admin) allowed -= allowed / 32; allowed += total_swap_pages; /* Don't let a single process grow too big: leave 3% of the size of this process for other processes */ if (mm) allowed -= mm->total_vm / 32; if (percpu_counter_read_positive(&vm_committed_as) < allowed) return 0; error: vm_unacct_memory(pages); return -ENOMEM; }
綜上:
最后把設備的overcommit_memory改為1,理由是:
(1)如果overcommit_memory為0的話, overcommit_ratio不啟用的。
(2)如果overcommit_memory為2的話,因為程序占用的虛擬內存是遠大於物理內存的,我們也無法在事前估算程序所占的虛擬內存,如果提前設置好一個值,那么還是會出現內存有剩余但是out of memory,那么本質的問題還是未能解決。
(3)如果overcommit_memory 為1的話可能會觸發OOM(有策略的殺掉用戶進程),但這也是系統的一種保護機制,防止設備掛掉。
(4)另外虛擬內存又不值錢,該用多少就用多少。
overcommit_ratio是什么?
當overcommit_memory=2的時候,它一般是代表的是系統中總的內存的百分比
虛擬內存CommitLimit計算:
CommitLimit = SwapTotal + MemTotal * overcommit_ratio
總的虛擬內存 = 總的交換分區 + 總的物理內存 * overcommit_ratio
這些信息可以到cat /proc/meminfo中看到, 可以通過上述的計算公式可以計算就可以獲得系統的CommitLimit的值
解決方法:
很簡單,按提示的操作(將vm.overcommit_memory 設為1)即可:
有三種方式修改內核參數,但要有root權限:
(1)編輯/etc/sysctl.conf ,改vm.overcommit_memory=1,然后sysctl -p使配置文件生效
(2)sysctl vm.overcommit_memory=1
(3)echo 1 > /proc/sys/vm/overcommit_memory
