某次遇到一个客户尝试用 Java (其实跟具体用什么语言没关系)申请使用 4G 的内存申请,机器(ECS)总内存是 8G,free 的内存也超过 4G,按道理是 OK 的,但总是直接 OOM。
于是便找上门来说,“你们这玩意有问题啊?”
内心 :“bug 是不可能有的,一定是你的打开姿势不对”,恩,不行,本着“客户第一”的原则,还是要来帮客户解锁姿势的。
本文就详细记录了这个 case 的排查过程。
申请4g内存失败
如上图所示,记录显示为申请 4G 内存失败(4294967296 B / 1024 / 1024 = 4096 M
)。
min_free_kbytes & nr_hugepage
配置错误?vm.min_free_kbytes & nr_hugepage
导致的free大于available案例有关memfree
统计的是所有内存的 free
内存,而 memavailable
统计的是可以拿来给程序用的内存,而客户设置了 vm.min_free_kbytes(2.5G)
,这个内存在 free
统计,但是不在 memavailable
统计,nr_hugepage
也会有这个问题。
二者的统计方式不一样, 具体参考 Documentation/filesystems/proc.txt
free -m && sysctl -p && /proc/meminfo
等信息分析问题。HugePages_Total
为0,说明没有设置 nr_hugepage
。MemAvailable: 7418172 kB
, 说明这么多内存可用。# sysctl -p
net.ipv4.ip_forward = 0
net.ipv4.conf.default.accept_source_route = 0
kernel.sysrq = 1
kernel.core_uses_pid = 1
net.ipv4.tcp_syncookies = 1
...
net.ipv4.tcp_tw_recycle=1
net.ipv4.tcp_max_syn_backlog=4096
net.core.netdev_max_backlog=10000
vm.overcommit_memory=2
...
#cat /proc/meminfo
MemTotal: 8009416 kB
MemFree: 7347684 kB
MemAvailable: 7418172 kB
Buffers: 18924 kB
Cached: 262836 kB
SwapCached: 0 kB
Active: 315188 kB
Inactive: 222364 kB
Active(anon): 256120 kB
Inactive(anon): 552 kB
Active(file): 59068 kB
Inactive(file): 221812 kB
....
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 114560 kB
DirectMap2M: 4079616 kB
DirectMap1G: 6291456 kB
java
命令,去申请超出我的测试机物理内存,拿到报错。实际上面的meminfo已经说明了问题,但是由于经验不足,一时没有看明白怎么回事。
下面测试证明正常申请内存不会有问题,超额的内存才会 OOM。
[root@test ~]# java -Xms4096M -version
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (build 1.8.0_242-b08)
OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode)
[root@test ~]# java -Xms5000M -version
OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x0000000687800000, 3495428096, 0) failed; error='Cannot allocate memory' (errno=12)
......
系统信息如下:
--------------- S Y S T E M ---------------
OS:CentOS Linux release 7.4.1708 (Core)
uname:Linux 3.10.0-693.2.2.el7.x86_64 #1 SMP Tue Sep 12 22:26:13 UTC 2017 x86_64
libc:glibc 2.17 NPTL 2.17
rlimit: STACK 8192k, CORE 0k, NPROC 15088, NOFILE 65535, AS infinity
load average:0.05 0.05 0.05
/proc/meminfo:
MemTotal: 3881692 kB
MemFree: 2567724 kB
MemAvailable: 2968640 kB
Buffers: 69016 kB
Cached: 536116 kB
SwapCached: 0 kB
Active: 355280 kB
Inactive: 326020 kB
...
VmallocTotal: 34359738367 kB
VmallocUsed: 14280 kB
VmallocChunk: 34359715580 kB
HardwareCorrupted: 0 kB
AnonHugePages: 30720 kB
HugePages_Total: 256
HugePages_Free: 256
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 57216 kB
DirectMap2M: 3088384 kB
DirectMap1G: 3145728 kB
....
Memory: 4k page, physical 3881692k(2567600k free), swap 0k(0k free)
vm_info: OpenJDK 64-Bit Server VM (25.242-b08) for linux-amd64 JRE (1.8.0_242-b08), built on Jan 28 2020 14:28:22 by "mockbuild" with gcc 4.8.5 20150623 (Red Hat 4.8.5-39)
time: Thu Feb 20 15:13:30 2020
timezone: CST
elapsed time: 0 seconds (0d 0h 0m 0s)
Java
测试证明正常申请内存不会有问题,超额的内存才会 OOM,那么为什么超额呢,视线回归到 sysctl -p
有所发现。vm.overcommit_memory=2
关于 overcommit_memory
设置项:
默认设置,当应用进程尝试申请内存时,内核会做一个检测。内核将检查是否有足够的可用内存供应用进程使用;
如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
举个例子,比如1G的机器,A进程已经使用了500M,当有另外进程尝试malloc 500M的内存时,内核就会进行check,发现超出剩余可用内存,就会提示失败。
对于内存的申请请求,内核不会做任何check,直到物理内存用完,触发 OOM 杀用户态进程。
同样是上面的例子,1G 的机器,A进程500M,B进程尝试 malloc 500M,会成功,但是一旦kernel发现内存使用率接近1个G(内核有策略),就触发OOM,杀掉一些用户态的进程(有策略的杀)。
当请求申请的内存 >= SWAP内存大小 + 物理内存 * N,则拒绝此次内存申请。解释下这个N:N是一个百分比,根据overcommit_ratio/100
来确定,比如overcommit_ratio=50
(我的测试机默认50%),那么N就是50%。 vm.overcommit_ratio
只有当 vm.overcommit_memory = 2
的时候才会生效,内存可申请内存为 SWAP内存大小 + 物理内存 * overcommit_ratio/100
。
看看上面日志的 overcommit
信息:
具体而言:
vm.overcommit_memory=2
时候生效),具体的值是:SWAP内存大小(ecs均未开启) + 物理内存 * overcommit_ratio / 100;5,两相对照,说明客户设置的 vm.overcommit_memory
在生效,建议改回 0
再试试。
vm.overcommit_memory = 2
测试,分配内存失败;[root@test ~]# grep -i commit /proc/meminfo
CommitLimit: 1940844 kB
Committed_AS: 480352 kB
# java -Xms2048M -version 失败了
OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x0000000080000000, 1431830528, 0) failed; error='Cannot allocate memory' (errno=12)
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 1431830528 bytes for committing reserved memory.
# An error report file with more information is saved as:
# /root/hs_err_pid1267.log
vm.overcommit_memory = 0, vm.overcommit_ratio = 50
#vm.overcommit_memory = 0
#vm.overcommit_ratio = 50
[root@test ~]# java -Xms2048M -version
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (build 1.8.0_242-b08)
OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode)
觉得本号分享的文章有价值,记得添加星标哦。周更很累,不要白 piao,需要来点正反馈,安排个 “一键三连”(点赞、在看、分享)如何?? 这将是我持续输出优质文章的最强动力。