背景
Memory Overcommit的意思是操作系统承诺给进程的内存大小超过了实际可用的内存。一个保守的操作系统不会允许memory overcommit,有多少就分配多少,再申请就没有了,这其实有些浪费内存,因为进程实际使用到的内存往往比申请的内存要少,比如某个进程malloc()了200MB内存,但实际上只用到了100MB,按照UNIX/Linux的算法,物理内存页的分配发生在使用的瞬间,而不是在申请的瞬间,也就是说未用到的100MB内存根本就没有分配,这100MB内存就闲置了。
下面这个概念很重要,是理解memory overcommit的关键:
commit(或overcommit)针对的是内存申请,内存申请不等于内存分配,内存只在实际用到的时候才分配。
Linux是允许memory overcommit的,只要你来申请内存我就给你,寄希望于进程实际上用不到那么多内存,但万一用到那么多了呢?那就会发生类似“银行挤兑”的危机,现金(内存)不足了。Linux设计了一个OOM killer机制(OOM = out-of-memory)来处理这种危机:挑选一个进程出来杀死,以腾出部分内存,如果还不够就继续杀…也可通过设置内核参数 vm.panic_on_oom
使得发生OOM时自动重启系统。
内存超分内核参数
内核有几个跟内存超分配相关的参数,可以用sysctl查看和修改:
# sysctl -a 2>/dev/null|grep overcommit
vm.nr_overcommit_hugepages = 0
vm.overcommit_kbytes = 0
vm.overcommit_memory = 0
vm.overcommit_ratio = 50
或者也可以通过procfs查看和修改:
# ls -l /proc/sys/vm/overcommit*
-rw-r--r-- 1 root root 0 Jan 3 17:29 /proc/sys/vm/overcommit_kbytes
-rw-r--r-- 1 root root 0 Dec 24 10:44 /proc/sys/vm/overcommit_memory
-rw-r--r-- 1 root root 0 Jan 3 17:29 /proc/sys/vm/overcommit_ratio
vm.overcommit_memory
- 0 – Heuristic overcommit handling. 这是缺省值,它允许overcommit,但过于明目张胆的overcommit会被拒绝,比如malloc一次性申请的内存大小就超过了系统总内存。Heuristic的意思是“试探式的”,内核利用某种算法(对该算法的详细解释请看文末)猜测你的内存申请是否合理,它认为不合理就会拒绝overcommit。
- 1 – Always overcommit. 允许overcommit,对内存申请来者不拒。
- 2 – Don’t overcommit. 禁止overcommit。
Memory Commit
当设置vm.overcommit_memory=2就关闭了overcommit ,那么怎样才算是overcommit呢?kernel设有一个阈值,申请的内存总数超过这个阈值就算overcommit,在/proc/meminfo中可以看到这个阈值的大小. CommitLimit
就是overcommit的阈值,申请的内存总数超过CommitLimit的话就算是overcommit。
# grep -i commit /proc/meminfo
CommitLimit: 5967744 kB
Committed_AS: 5363236 kB
这个阈值是如何计算出来的呢?它既不是物理内存的大小,也不是free memory的大小,它是通过内核参数vm.overcommit_ratio
或vm.overcommit_kbytes
间接设置的,公式如下:
CommitLimit = (Physical RAM * vm.overcommit_ratio / 100) + Swap
vm.overcommit_ratio
是内核参数,缺省值是50,表示物理内存的50%。
/proc/meminfo中的Committed_AS
表示所有进程已经申请的内存总大小,(注意是已经申请的,不是已经分配的),如果 Committed_AS
超过 CommitLimit
就表示发生了 overcommit,超出越多表示 overcommit 越严重。Committed_AS 的含义换一种说法就是,如果要绝对保证不发生OOM (out of memory) 需要多少物理内存。
Atop工具
atop是一个我常用的系统状态查看工具,其中的vmcom(Virtual Memory Commit)即反映了Committed_AS
, 而vmlim(Virtual Memory Limit)反映了CommitLimit
, 当vmcom大于vmlim的时候,atop会高亮vmcom作为告警,表示这个时候系统会有oom的可能。
PRC | sys 0.53s | user 0.65s | #proc 621 | #trun 2 | #tslpi 807 | #tslpu 0 | #zombie 0 | clones 1242 | #exit 1230 |
CPU | sys 5% | user 9% | irq 0% | idle 4787% | wait 0% | steal 0% | guest 0% | curf 2.70GHz | curscal ?% |
CPL | avg1 0.82 | avg5 0.89 | avg15 0.97 | | csw 69558 | | intr 59138 | | numcpu 48 |
MEM | tot 187.6G | free 161.1G | cache 18.1G | buff 534.0M | slab 1.0G | shmem 193.6M | vmbal 0.0M | hptot 0.0M | hpuse 0.0M |
SWP | tot 0.0M | free 0.0M | | | | | | vmcom 25.2G | vmlim 93.8G |
验证
Demo程序
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (void) {
int n = 0;
char *p;
while (1) {
if ((p = malloc(1<<20)) == NULL) {
printf("malloc failure after %d MiB\n", n);
return 0;
}
memset (p, 0, (1<<20));
printf ("got %d MiB\n", ++n);
}
}
4 GB RAM, 4 GB Swap, overcommit_memory = 2:
Overcommit Ratio | MemFree (kB) | CommitLimit (kB) | Breaks at (MB) | Expected break (MB) |
---|---|---|---|---|
10 | 3803668 | 4595144 | 4200 | 4488 |
25 | 3802056 | 5199488 | 4793 | 5078 |
50 | 3801852 | 6206724 | 5771 | 6062 |
75 | 3802732 | 7213960 | 6748 | 7045 |
90 | 3802620 | 7818300 | 7340 | 7636 |
100 | 3802888 | 8221196 | 7729 | 8029 |
4 GB RAM, no Swap, overcommit_memory = 2:
Overcommit Ratio | MemFree (kB) | CommitLimit (kB) | Breaks at (MB) | Expected break (MB) |
---|---|---|---|---|
10 | 3803964 | 402892 | 243 | 394 |
25 | 3802532 | 1007236 | 803 | 984 |
50 | 3799844 | 2014472 | 1756 | 1968 |
75 | 3803580 | 3021708 | 2708 | 2951 |
90 | 3805424 | 3626048 | 3276 | 3542 |
100 | 3804236 | 4028944 | 3653 | 3935 |
4 GB RAM, 4 GB Swap, overcommit_memory = 1:
Overcommit Ratio | MemFree (kB) | CommitLimit (kB) | Breaks at (MB) |
---|---|---|---|
10 | 3803952 | 4595144 | 7794 |
25 | 3804852 | 5199488 | 7804 |
50 | 3804564 | 6206724 | 7800 |
75 | 3805032 | 7213960 | 7800 |
90 | 3802900 | 7818300 | 7803 |
100 | 3801392 | 8221196 | 7748 |