99%的人都不知道内存充足情况下也会触发OOM!

【问题背景】

一台虚拟机crash,从物理机的日志看,触发了oom,于是把虚拟机给kill了,但是事后通过监控查看物理机上的内存空间是比较充足的,尚有40GB的可用内容。

那么问题来了,为什么明明内存足够,缺无法分配?

【问题服务器所在架构】

1台48C256GB的Intel x86服务器(开了超线程,实际为2个物理CPU,24core),在上面创建了两个虚拟机,每个虚拟机为提高性能,采用了vCPU绑核的方式进行了绑定,每个物理CPU(24C中配置23C)对应一个虚拟机。虚拟化采用了qemu-kvm的技术。故每台虚拟机的配置为23C120GB。

【报错日志分析】

oom日志分析

第一段:qemu-kvm需要申请内存,但是发现内存不够,于是触发了oom,触发本次oom的进程PID为2905469.

相应的调用函数为从下往上:

page_fault -> do_page_fault ->...->oom_kill_process->...->dump_stack

汇总当前节点的内存使用情况,Node 1上空余内存为40MB。

开始对当前系统的进程进行排序,最终选择score分数为782(最高)的进程pid 2905469作为第一个释放的pid进行killed。

也就是自己杀死了自己。GG

【寻找蛛丝马迹】

翻了一下numa架构,正常情况下,numa架构下,应该有Node0和Node1两个内存节点,每个内存节点128GB。

正常情况下,即便是进程绑定了CPU,也是代表说CPU优先使用对应NODE中的内存空间,一旦该NODE的内存空间不足了,就会使用另外一个NODE的内存。

然而在本次的oom过程中,只有收集了NODE 1的free内存。并没有手机NODE 0的free内存。这一点也就是事后为什么在监控上还能看到物理机内存剩余空间比较大的原因。这部分free的内存实际来源于NODE 0。

从oom的日志来看,应该是这台虚拟机的配置存在一些特殊情况导致了内存只在NODE 1中进行分配。

进一步分析虚拟机的xml文件,看到有如下配置:

<numatune>    <memory mode="strict" nodeset="1"/>  </numatune>

【根因分析】

numatune是libvirt的一个参数,可以用在numa架构的host上,以控制虚拟机的内存访问策略。

numatune的mode选项有:

  1. strict:默认的策略,如果值指定的node上无法分配内存,则虚拟机

  2. interleave:通过轮询方式,在指定的多个node上分配内存

  3. preferred:优先在指定node上分配内存,如果内存不足,允许在其他node上分配内存

由此,可以看到当前服务器上的xml配置为nodeset=1且mode为strict,因此虚拟机只能使用NODE1上面的内存,也就是128GB,一旦128GB用完了,那么这个虚拟机将无足够的内存使用。

虽然两个虚拟机分别绑定了两个不同的内存NODE,但是除了虚拟机之外,操作系统上还有其他的openstack进程和系统进程也会使用内存,而且分布可能不均衡,因此在长期的运行下,随着内存实际使用率越来越高,最终触发了OOM。

【衍生阅读】

内存页面分配-快速路径icon-default.png?t=N7T8http://mp.weixin.qq.com/s?__biz=MzU0MjYxMjIxMg==&mid=2247484209&idx=1&sn=0b859c80604d3f52712f53f038e17b52&chksm=fb194caacc6ec5bc454b796e0fe1adb43e6d7acd2b7feea205b50cb7903856e28ce80926dd4b&scene=21#wechat_redirect

内存页面分配-慢速路径icon-default.png?t=N7T8http://mp.weixin.qq.com/s?__biz=MzU0MjYxMjIxMg==&mid=2247484236&idx=1&sn=b5553a9b24885a9f230be9bb567456fd&chksm=fb194cd7cc6ec5c13d648e26777077b416095fb05d4555402be19bf62bc7024c7f4bb9afbf5e&scene=21#wechat_redirect

内存水位管理和分配优先级icon-default.png?t=N7T8http://mp.weixin.qq.com/s?__biz=MzU0MjYxMjIxMg==&mid=2247484242&idx=1&sn=a8f59744b76fed4180cee3830f45d96b&chksm=fb194cc9cc6ec5dfd30f0cc9d32e3dd93fce2c49758a0a2dfc4ab6ba30967db238db8acf2da7&scene=21#wechat_redirect

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM中一共有以下几个内存区域:程序计数器、虚拟机栈、本地方法栈、堆、方法区(也称为永久代或元空间)。 1. 程序计数器:程序计数器是线程私有的,用来记录下一条指令的地址。程序计数器不触发OOM。 2. 虚拟机栈和本地方法栈:虚拟机栈和本地方法栈也是线程私有的,用来存储方法执行时的局部变量、操作数栈、方法出口等信息。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈无法动态扩展或者无法分配到足够内存空间,则抛出OutOfMemoryError异常。 3. 堆:堆是所有线程共享的一块内存区域,用来存储对象实例。如果堆中没有足够内存空间来分配新的对象实例,则抛出OutOfMemoryError异常。 4. 方法区:方法区也是所有线程共享的一块内存区域,用来存储类的元数据信息、常量池、静态变量等。如果方法区无法满足内存分配需求,则抛出OutOfMemoryError异常。 下面是一个内存泄漏的示例,通过不断创建对象并将其添加到List集合中,最终导致堆内存溢出。 ```java import java.util.ArrayList; import java.util.List; public class OOMDemo { static class OOMObject { // 一个占用较大内存的对象 private byte[] bytes = new byte[1024 * 1024]; } public static void main(String[] args) { List<OOMObject> list = new ArrayList<>(); while (true) { list.add(new OOMObject()); } } } ``` 在上述示例中,每次添加一个OOMObject对象到List集合中,但是这些对象不被使用到,因此占用的内存被释放。随着对象数量的增加,堆内存空间逐渐被耗尽,最终导致OutOfMemoryError异常的抛出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值