Memory OverCommit介绍
首先,区分一下内存的申请和分配两个概念,申请内存只是内核给应用程序承诺的内存空间,并没有实际分配内存,实际内存页分配发生在内存使用的瞬间,而不是申请的瞬间。比如,调用malloc函数仅仅是向内核申请了固定大小的空间,而只有应用程序实际使用该内存块时,比如调用memset函数做初始化,内核才会真正分配物理内存给应用程序。
很多应用程序(比如数据库)习惯于一次性申请大量的内存空间,而有些内存块可能在程序的整个生命周期都没有被用到,如果最多只允许申请实际可用的空间大小,就会导致内存浪费,明明系统有未使用的内存,而其他程序却无法申请得到。Memory OverCommit功能允许内存申请过度提交,也就是允许应用程序申请超过实际可用大小的内存空间,注意这里的commit也是针对的是内存申请,所以通过malloc函数可以申请远超过实际物理内存的空间。Linux系统是允许Memory OverCommit的。
如果应用程序实际使用(需要分配)的内存空间超过了实际空间大小怎么办呢?Linux设计了一个OOM killer机制(OOM = out-of-memory)来处理这种情况:选择一些进程强行终止来释放一部分内存空间,也可通过设置内核参数 vm.panic_on_oom 使得发生OOM时自动重启系统。
Linux OverCommit
Linux对于是否启用OverCommit有一些相关的内核参数设置:
vm.overcommit_memory,这个参数如下有3种取值:
0 --Heuristic overcommit handling. 这是缺省值,它允许overcommit。内核会对应用程序的内存申请做检查,当申请的内存超过剩余可分配空间时,应用程序申请内存的操作将会失败,比如调用malloc函数返回空指针,而应用程序将会处理这种错误。如果应用程序只是持续申请内存空间,即使始终不使用(没有实际分配),当申请的空间到达一个很大的值时,程序进程会被OOM Killer强行终止。
1 --允许OverCommit。内核不检查应用程序的内存申请,也就意味着malloc函数的调用不会因为空间不足而失败。但是,malloc即使申请成功并不代表这块内存就真的可用,在实际分配内存时可能会遇到空间不足,此时内核检测到内存被用完,OOM Killer将会选择一些进程强行终止来释放一部分内存空间。
2 --禁用OverCommit。内核根据CommitLimit限制实际可以申请的内存空间。涉及到的配置包括:
vm.overcommit_memory可以直接用echo命令修改:
echo 0 > /proc/sys/vm/overcommit_memory
CommitLimit的计算公式:
CommitLimit = Swap space + Memory space * (Ratio / 100)
Ratio保存在/proc/sys/vm/overcommit_ratio中,1 <= Ratio <= 100,可以直接用echo命令修改:
echo 50 > /proc/sys/vm/overcommit_ratio
比如系统:4GB Mem,4GB Swap;OverCommit = 2; Ratio = 50;则限制申请的内存大小为:
4GB * (50 / 100) + 4GB = 6GB
这个值也就是CommitLimit的大小,可以在/proc/meminfo中查看,它随着Ratio的改变而实时改变。注意:只有当vm.overcommit_memory设置为2时,CommitLimit才会被用到。
Notes:
1. 有些专用系统,比如数据库服务器,为了提升性能不想用到swap,则Ratio的的设置规则为:
Ratio = 100 * ((Memory Space - Swap Space) / Memory Space)
2. 当vm.overcommit_memory设置为2时,如果扩展了物理内存,一定要记得重新计算并修改Ratio的值,否则内存空间可能无法得到充分利用。在References来自pivotal的文章中对此有详细的描述。
Demo
我们用一些小程序来对Linux OverCommit做一些测试:
测试环境
$ cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.6 (Maipo)
$ uname -r
3.10.0-957.12.2.el7.x86_64
$ free
total used free shared buff/cache available
Mem: 12040064 2442408 8843480 7876 754176 5951044
Swap: 15359996 4916760 10443236
在我的测试系统中:
- 物理内存总共11757MB,Swap空间总共有14999MB。
- 物理内存和Swap总共的剩余空间在18GB左右。
在后面的demo中,我不打算做准确的定量计算,只是做定性分析。
Demo1--申请内存后初始化
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (void) {
int n = 0;
char *p;
while (1) {
if ((p = malloc(1<<27)) == NULL) {
printf("malloc failure after %d MB\n", n);
return 0;
}
memset (p, 0, (1<<27));
n += 128;
printf ("got %d MB\n", n);
}
}
在这个程序中,我选择了每次申请128MB内存,并在申请成功后立即初始化。
vm.overcommit_memory = 0
程序终止于:
malloc failure after 16512 MB
注意,这里程序是因为malloc失败而正常退出,说明对内存申请OverCommit的检查生效了。
vm.overcommit_memory = 1
程序终止于:
$ ./test_CommiLimit_2 &
[1] 331755
..
got 17536 MB
[1]+ Killed ./test_CommiLimit_2
这里程序退出是因为内存不足,被OOM-killer强制kill,在/var/log/messages中可以看到OOM-killer的工作过程:
Jul 15 19:16:50 n8-h29 kernel: test_CommiLimit invoked oom-killer: gfp_mask=0x280da, order=0, oom_score_adj=0
Jul 15 19:16:51 n8-h29 kernel: test_CommiLimit cpuset=/ mems_allowed=0
Jul 15 19:16:51 n8-h29 kernel: CPU: 5 PID: 331755 Comm: test_CommiLimit Kdump: loaded Tainted: P OE ------------ 3.10.0-957.12.2.el7.x86_64 #1
Jul 15 19:16:51 n8-h29 kernel: Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 12/12/2018
Jul 15 19:16:51 n8-h29 kernel: Call Trace:
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff93763041>] dump_stack+0x19/0x1b
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff9375da6a>] dump_header+0x90/0x229
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff93101212>] ? ktime_get_ts64+0x52/0xf0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931ba7b4>] oom_kill_process+0x254/0x3d0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931ba25d>] ? oom_unkillable_task+0xcd/0x120
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931ba306>] ? find_lock_task_mm+0x56/0xc0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931baff6>] out_of_memory+0x4b6/0x4f0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff9375e56e>] __alloc_pages_slowpath+0x5d6/0x724
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931c13d4>] __alloc_pages_nodemask+0x404/0x420
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff93211715>] alloc_pages_vma+0xb5/0x200
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931e9c57>] handle_pte_fault+0x887/0xd10
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931ec1fd>] handle_mm_fault+0x39d/0x9b0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff93770603>] __do_page_fault+0x203/0x4f0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff93770925>] do_page_fault+0x35/0x90
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff9376cab6>] ? error_swapgs+0xa7/0xbd
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff9376c768>] page_fault+0x28/0x30
Jul 15 19:16:51 n8-h29 kernel: Mem-Info:
Jul 15 19:16:51 n8-h29 kernel: active_anon:1756967 inactive_anon:319617 isolated_anon:0#012 active_file:1864 inactive_file:6745 isolated_file:32#012 unevictable:21668 dirty:0 writeback:131 unstable:0#012 slab_reclaimable:11340 slab_unreclaimable:34853#012 mapped:2233 shmem:488 pagetables:18847 bounce:0#012 free:536526 free_pcp:781 free_cma:0
...
Jul 15 19:16:55 n8-h29 kernel: Out of memory: Kill process 331755 (test_CommiLimit) score 637 or sacrifice child
Jul 15 19:16:55 n8-h29 kernel: Killed process 331755 (test_CommiLimit) total-vm:18092700kB, anon-rss:8004720kB, file-rss:392kB, shmem-rss:4kB
vm.overcommit_memory = 2
vm.overcommit_ratio = 50
$ cat /proc/meminfo | grep Commit
CommitLimit: 21380028 kB
Committed_AS: 17967096 kB
CommitLimit = 12040064 * (50 / 100) + 15359996 = 21380028 KB = 20879 MB
Committed_AS代表已经申请的内存空间,大概有17546 MB,所以留给测试程序申请的最大空间为20897 MB - 17546 MB = 3333 MB。这并不代表我们的程序还可以申请这么多空间,实际申请到的往往比3333 MB小,排除测试系统剩余内存空间并不稳定的因素,测试程序本身包括其引用的库也会占用一部分内存。
$ ./test_CommiLimit_2 &
[1] 338176
...
malloc failure after 3200 MB
[1]+ Done ./test_CommiLimit_2
Demo2--只申请不使用内存
#include <stdio.h>
#include <stdlib.h>
int main (void) {
int n = 0;
while (1) {
if (malloc(1<<24) == NULL) {
printf("malloc failure after %d MB\n", n);
return 0;
}
n +=16;
printf ("got %d MB\n", n);
}
}
在这个程序中,我们只是循环调用malloc申请内存,每次申请16MB,但是不使用申请的内存。
vm.overcommit_memory = 0
程序终止于:
got 32237728 MB
var/log/messages
Jul 15 19:55:04 n8-h29 kernel: Out of memory: Kill process 343274 (test_CommiLimit) score 576 or sacrifice child
Jul 15 19:55:04 n8-h29 kernel: Killed process 343274 (test_CommiLimit) total-vm:33335085348kB, anon-rss:0kB, file-rss:400kB, shmem-rss:0kB
vm.overcommit_memory = 1
程序终止于:
got 32540832 MB
总之,即便是只申请内存不使用,在申请的空间达到一个很大的值时,也会被OOM-killer杀掉。
References
http://engineering.pivotal.io/post/virtual_memory_settings_in_linux_-_the_problem_with_overcommit/