一、前言

最近公司线上出了故障,有业务反馈说线上某台机器发出的请求status都是101,代表是超时。于是顺着调用栈和监控去查,最后发现这台机器上的网关挂掉了,所以导致请求发不出去,导致业务超时。那为啥无缘无故的网关挂掉了呢,顺着各种系统日志去查,结合机器是mq消费脚本专用机器,最后从系统日志/var/log/messages中查到如下日志

...
Out of memory: Kill process 9682 (mysqld) score 9 or sacrifice child
Killed process 9682, UID 27, (mysqld) total-vm:47388kB, anon-rss:3744kB, file-rss:80kB
httpd invoked oom-killer: gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0
httpd cpuset=/ mems_allowed=0
Pid: 8911, comm: httpd Not tainted 2.6.32-279.1.1.el6.i686 #1
...

现在才意识到,该机器是跑mq脚本的固定机器,由于进程开的太多导致内存占用太大,导致内存不够从而发生OOM kill问题。

二、深入理解OOM

2.1 Linux OverCommit

Linux下允许程序申请比系统可用内存更多的内存(如malloc函数),这个特性叫Overcommit。这么做是出于优化系统的考虑,因为并不是所有的程序申请了内存就立刻使用,当使用的时候说不定系统已经回收了一些内存资源了。不过当需要真正使用内存资源而系统已经没有多余的内存资源可用时,OOM机制就被触发了。

Linux下有3种Overcommit策略,可以通过/proc/sys/vm/overcommit_memory配置,取0、1和2三个值,默认是0:

>取值0:启发式策略,比较多的内存申请可能会被拒绝,如当前内存2G,突然申请1T的内存(一般当系统启动selinux模块时有效,其他情况等同取值1);

>取值1:允许分配比当前内存资源多的内存;

>取值2:系统所能分配的内存资源不能超过swap+内存资源*系数(/proc/sys/vm/overcommit_ratio,默认50%,可调整)。如果资源已经用光,再有内存申请请求时,都会返回错误。

2.2  oom_killer

oom_killer(out of memory killer)是Linux内核的一种内存管理机制,在系统可用内存较少的情况下,内核为保证系统还能够继续运行下去,会选择杀掉一些进程释放掉一些内存。通常oom_killer的触发流程是:进程A想要分配物理内存(通常是当进程真正去读写一块内核已经“分配”给它的内存)->触发缺页异常->内核去分配物理内存->物理内存不够了,触发OOM

一句话说明oom_killer的功能:当系统物理内存不足时,oom_killer遍历当前所有进程,根据进程的内存使用情况进行打分,然后从中选择一个分数最高的进程,杀之取内存。

2.3 OOM kill策略

Linux下每个进程都有一个OOM权重,在/proc/<pid>/oom_adj里面,取值是-17到+15(为-17此进程不会被杀掉),取值越高,越容易被杀掉。

最终OOM-Killer是通过/proc/<pid>/oom_score这个值来决定哪个进程被杀死。这个值是系统综合进程的内存消耗量、CPU时间(utime+stime)、存活时间(utime - start_time)和oom_adj计算出的,消耗内存越多oom_score值越高,存活时间越长值越低。

另外,Linux在计算进程的内存消耗的时候,会将子进程所耗内存的一半算到父进程中.

总之,OOM-Killer策略是:损失最少的工作,释放最大的内存;同时不伤及无辜的用了很大内存的进程,并且杀掉的进程数尽量少。

2.4 OOM Kill 实现机制

(1) 查找/proc/sys/vm/panic_on_oom设置,如果值为2,引起Kernel Panic内核恐慌则会停止掉所有进程10s自动重启系统;一般默认为0。

(2) 判断/proc/sys/vm/oom_kill_allocating_task设置:为1,直接将当前进程杀死,默认为0。

(3) 获取/proc/sys/vm/overcommit_memory中的配置的值: 如果值为2,直接将当前进程杀死;为0:判断panic_on_oom是否有值,有值直接panic,否则进入下一步。

(4) 判断/proc/sys/vm/would_have_oomkilled设置:值为1,也不会真正的去杀死进程。

(5) 判断接口/proc/<pid>/oom_adj设置值为-17,表示该进程不可被杀死。

(6) 接下来调用 select_bad_process() 选择一个“bad”进程杀掉,判断和选择一个“bad”进程的过程由 oom_badness()决定,最 bad 的那个进程就是那个最占用内存的进程。

(7) root 权限的进程通常被认为很重要,不应该被轻易杀掉,所以打分的时候可以得到 3% 的优惠(分数越低越不容易被杀掉);可以在用户空间通过操作每个进程的 oom_adj 内核参数来决定哪些进程不这么容易被 OOM killer 选中杀掉;如果计算分数为0也就是告知OOM killer,该进程是“good process”,不要干掉它。

(8) 先杀死当前进程的子进程,在关闭父进程。

(9) 当内存资源紧张,同时也没有可以杀死的进程时,系统会panic;

2.5 选择bad进程函数实现

static void __out_of_memory(gfp_t gfp_mask, int order){struct task_struct *p;unsigned long points;//如果sysctl_oom_kill_allocating_task值设置了,就会直接杀掉申请内存的进程。if (sysctl_oom_kill_allocating_task)if (!oom_kill_process(current, gfp_mask, order, 0, NULL,"Out of memory (oom_kill_allocating_task)"))return;retry:/** Rambo mode: Shoot down a process and hope it solves whatever* issues we may have.*/p = select_bad_process(&points, NULL);if (PTR_ERR(p) == -1UL)return;/* Found nothing?!?! Either we hang forever, or we panic. */if (!p) {read_unlock(&tasklist_lock);panic("Out of memory and no killable processes...\n");}if (oom_kill_process(p, gfp_mask, order, points, NULL,"Out of memory"))goto retry;static struct task_struct *select_bad_process(unsigned long *ppoints,struct mem_cgroup *mem)
{struct task_struct *p;struct task_struct *chosen = NULL;struct timespec uptime;*ppoints = 0;do_posix_clock_monotonic_gettime(&uptime);for_each_process(p) {//遍历所有的进程包括用户进程和内核进程unsigned long points;/** skip kernel threads and tasks which have already released* their mm. 跳过内核进程*/if (!p->mm)continue;/* skip the init task 跳过Init进程*/if (is_global_init(p))continue;if (mem && !task_in_mem_cgroup(p, mem))continue;if (test_tsk_thread_flag(p, TIF_MEMDIE))return ERR_PTR(-1UL);if (p->flags & PF_EXITING) {if (p != current)return ERR_PTR(-1UL);chosen = p;*ppoints = ULONG_MAX;}
//这里就是 #define OOM_DISABLE (-17) 也就是/proc/<pid>/oom_adj这个值if (p->signal->oom_adj == OOM_DISABLE)continue;
//对其它的进程调用badness()函数来计算相应的score,score最高的将被选中points = badness(p, uptime.tv_sec);if (points > *ppoints || !chosen) {chosen = p;*ppoints = points;}}return chosen;
}unsigned long badness(struct task_struct *p, unsigned long uptime){unsigned long points, cpu_time, run_time;struct mm_struct *mm;struct task_struct *child;int oom_adj = p->signal->oom_adj;struct task_cputime task_time;unsigned long utime;unsigned long stime;//如果OOM是被禁止的,则直接返回。if (oom_adj == OOM_DISABLE)return 0;task_lock(p);mm = p->mm;if (!mm) {task_unlock(p);return 0;}/** The memory size of the process is the basis for the badness.该进程占用的内存大小*/points = mm->total_vm;/** After this unlock we can no longer dereference local variable `mm'*/task_unlock(p);/** swapoff can easily use up all memory, so kill those first.*/if (p->flags & PF_OOM_ORIGIN)return ULONG_MAX;list_for_each_entry(child, &p->children, sibling) {task_lock(child);//如果该进程含有子进程,该进程子进程total_vm的一半加入到points中if (child->mm != mm && child->mm)points += child->mm->total_vm/2 + 1;task_unlock(child);}/** CPU time is in tens of seconds and run time is in thousands* of seconds. There is no particular reason for this other than* that it turned out to work very well in practice.*/thread_group_cputime(p, &task_time);utime = cputime_to_jiffies(task_time.utime);stime = cputime_to_jiffies(task_time.stime);cpu_time = (utime + stime) >> (SHIFT_HZ + 3);if (uptime >= p->start_time.tv_sec)run_time = (uptime - p->start_time.tv_sec) >> 10;elserun_time = 0;// score和进程的cpu_time以及run_time成反比,也就是该进程运行的时间越长,score值越低。if (cpu_time)points /= int_sqrt(cpu_time);if (run_time)points /= int_sqrt(int_sqrt(run_time));/** Niced processes are most likely less important, so double* their badness points. nice大于0的进程,score翻倍,nice的范围一般是-20~+19,值越大优先级越低。*/if (task_nice(p) > 0)points *= 2;/** Superuser processes are usually more important, so we make it* less likely that we kill those. 对设置了超级权限的进程降低score,具有超级权限的进程更加重要。*/if (has_capability_noaudit(p, CAP_SYS_ADMIN) ||has_capability_noaudit(p, CAP_SYS_RESOURCE))points /= 4;/** We don't want to kill a process with direct hardware access.* Not only could that mess up the hardware, but usually users* tend to only have this flag set on applications they think* of as important. 对设置了超级权限的进程降低score*/if (has_capability_noaudit(p, CAP_SYS_RAWIO))points /= 4;/** If p's nodes don't overlap ours, it may still help to kill p* because p may have allocated or otherwise mapped memory on* this node before. However it will be less likely.如果和p进程在内存上没有交集的进程降低score*/if (!has_intersects_mems_allowed(p))points /= 8;/** Adjust the score by oom_adj.最后是根据该进程的oom_adj进行移位操作,计算最终的score,这样根据各个策略就计算出来scope值,该值越大,进程被杀死的概率也就越高*/if (oom_adj) {if (oom_adj > 0) {if (!points)points = 1;points <<= oom_adj;} elsepoints >>= -(oom_adj);}#ifdef DEBUGprintk(KERN_DEBUG "OOMkill: task %d (%s) got %lu points\n",p->pid, p->comm, points);#endifreturn points;}static void __oom_kill_task(struct task_struct *p, int verbose){if (is_global_init(p)) {WARN_ON(1);printk(KERN_WARNING "tried to kill init!\n");return;}if (!p->mm) {WARN_ON(1);printk(KERN_WARNING "tried to kill an mm-less task!\n");return;}if (verbose)printk(KERN_ERR "Killed process %d (%s)\n",task_pid_nr(p), p->comm);p->rt.time_slice = HZ;set_tsk_thread_flag(p, TIF_MEMDIE);force_sig(SIGKILL, p);}

函数badness()就是根据各种条件进行判断,找到一个最应该杀死的进程。主要的选择条件是下面的几点:

(1)score初始值为该进程占用的total_vm;

(2)如果该进程有子进程,子进程独自占用的total_vm/2加到本进程score;

(3)score随着该进程的cpu_time以及run_time的增长而减少,也就是运行的时间越长,被kill掉的几率越小

(4) nice大于0的进程,score*2;

(5)对于拥有超级权限的进程,或者直接磁盘交互的进程降低score;

(6)如果和current进程在内存上没有交集,则该进程降低score;

(7)最后根据该进程的oom_adj,计算得出最终的score;

2.6 关闭OOM可以吗?

关闭OOM机制,虽然不会出现杀进程的现象,但是应用程序在申请内存资源时由于内存资源紧张导致申请不到资源,可能会出现一直循环申请内存,直到申请到为止,在未申请到内存资源之前,会一直占用某个CPU不放,导致机器卡住。

三、如何排查OOM问题

3.1 查看系统日志

查看系统日志/var/log/messages会发现Out of Memory: Kill process 1865(sshd)类似的错误信息。

也可以运行egrep -i -r 'killed process' /var/log 命令或者 dmesg 命令查看被杀掉进程的内存占用情况。

3.2 java排查

(1) 确认是不是内存本身就分配过小

方法:jmap -heap pid

(2) 找到最耗内存的对象

方法:jmap -histo:live pid | more

如果发现某类对象占用内存很大(例如几个G),很可能是类对象创建太多,且一直未释放。例如:

  • 申请完资源后,未调用close()或dispose()释放资源

  • 消费者消费速度慢(或停止消费了),而生产者不断往队列中投递任务,导致队列中任务累积过多

画外音:线上执行该命令会强制执行一次fgc。另外还可以dump内存进行分析。
(3) Linux命令行工具

查看进程创建的线程数,以及网络连接数,如果资源耗尽,也可能出现OOM

  • pstree

  • netstat

进程打开的句柄数和线程数

  • ll /proc/${PID}/fd | wc -l

  • ll /proc/${PID}/task | wc -l (效果等同pstree -p | wc -l)

四、如何避免发生OOM问题

4.1 防止重要的系统进程触发(OOM)机制而被杀死

可以设置参数/proc/PID/oom_adj为-17,可临时关闭linux内核的OOM机制。内核会通过特定的算法给每个进程计算一个分数来决定杀哪个进程,每个进程的oom分数可以/proc/PID/oom_score中找到。

使用的解决办法:

1、限制java进程的max heap,并且降低java程序的worker数量,从而降低内存使用

2、发现系统没有开启swap,给系统加了8G的swap空间

其它解决办法(不推荐),不允许内存申请过量:

# echo "2" > /proc/sys/vm/overcommit_memory

# echo "80" > /proc/sys/vm/overcommit_ratio

——————————————————————————————

参考来源:1、http://blog.chinaunix.net/uid-20788636-id-4308527.html

2、https://cloud.tencent.com/developer/article/1157275

3、https://blog.csdn.net/zgrjkflmkyc/article/details/77645570

OOM问题排查及原因解析相关推荐

  1. python运行不了control+shift+i_Python不支持 i ++ 语法的原因解析

    简要讨论为什么它不提供++作为运算符 正常情况下,当有人问起++原因而不是Python中的运算符时,这一行引起了我的注意. 如果您想知道最初的原因,则必须翻阅旧的Python邮件列表,或询问那里的某个 ...

  2. ORA-14452的出现原因解析及解决方法

    ORA-14452的出现原因解析及解决方法 参考文章: (1)ORA-14452的出现原因解析及解决方法 (2)https://www.cnblogs.com/kerrycode/p/3627334. ...

  3. python显示无效语法怎么处理-Python不支持 i ++ 语法的原因解析

    简要讨论为什么它不提供++作为运算符 正常情况下,当有人问起++原因而不是Python中的运算符时,这一行引起了我的注意. 如果您想知道最初的原因,则必须翻阅旧的Python邮件列表,或询问那里的某个 ...

  4. java break在switch_java中switch case语句需要加入break的原因解析

    java中switch case语句需要加入break的原因解析 java 中使用switch case语句需要加入break 做了具体的实例分析,及编译源码,在源码中分析应该如何使用,大家可以参考下 ...

  5. 【Java报错】借助@PostConstruct解决使用@Component注解的类用@Resource注入Mapper接口为null的问题(原因解析+解决方法)

    1. 说明 有些时候我们需要一个管理类,类似 xxxManager 来处理共享的基础数据,它要在项目启动时就进行初始化且查询数据库,而且查询语句不复杂,写一整套的Service或者使用MyBatis的 ...

  6. JVM发生OOM的 8 种原因、及解决办法

    转载自  JVM发生OOM的 8 种原因.及解决办法 1.Java 堆空间 发生频率:5颗星 造成原因 无法在 Java 堆中分配对象 吞吐量增加 应用程序无意中保存了对象引用,对象无法被 GC 回收 ...

  7. python jieba库下载_Python中jieba库安装步骤及失败原因解析

    Python 中 jieba 库安装步骤及失败原因解析 作为计算机小白, Python 的流行也让我蠢蠢欲动, 在请教计算机 专业同学后,开始上网课自学 Python 基础知识.今天老师简单的一 句话 ...

  8. nexbox本地网络调试工具下载_「下载」 Windows 10 WinDBG 分析转储日志和蓝屏日志排查错误原因...

    使用Windows 10相对来说出现蓝屏概率还是很高的,但微软提供的错误代码有时候可能无法帮助我们解决问题. 所以我们需要使用更专业的工具来分析系统记录的日志,有日志进行排查后就可以定位到具体什么原因 ...

  9. 辰皇怎么过鸿蒙,诛仙3最厉害的职业是什么 强弱对应原因解析

    诛仙3最厉害的职业是什么 强弱对应原因解析,游戏里玩家最想知道的就是什么职业最厉害,从而自己去玩哪个最厉害的职业.诛仙3最厉害的职业是什么 强弱对应原因解析,职业没有强弱之分只有玩家玩的还不会. 职业 ...

最新文章

  1. java android统计图_Android统计图表之柱状图(条形图)
  2. k8s滚动升级_k8s deployment 滚动更新
  3. mysql 二叉树表设计_mysql---B+tree索引的设计原理
  4. 可执行镜像——开发环境的Docker化之路
  5. android webview 太大,Android应用开发之Android WebView加载图片显示过大的处理教程(代码教程)...
  6. LOL易游网络验证破解总结
  7. RRRR_wys' Blog 3.0 准备上线啦!
  8. 6自由度机械臂建模与仿真
  9. unity中旋转的总结
  10. 目前最新《Thinkphp 5.0 仿百度糯米开发多商家电商平台》
  11. 简单了解下什么是中台?
  12. C++-容器-string:数字to字符串【std::to_string()】、字符串to数字【std::stoi、stol、stoll、stoul、stoull、stof、stod、stold】
  13. The server time zone value ‘‘ is unrecognized or represents more than one time zone.
  14. 基于3D Vision眼镜的OSG立体显示 【转】
  15. makefile(9) : fatal error U1052: 未找到文件Win32.Mak
  16. 怎么给自己的电脑连接打印机
  17. t检验只能用于样本量少于30个的数据?要做z检验吗?(转)
  18. windows系统删除无用的服务
  19. 半导体中载流子的统计分布
  20. 6-1 类模板Point的定义与使用 (10 分)

热门文章

  1. VERY CD 上的一些资源: 强人的PDF杂志集
  2. MySQL日期格式化参数大全
  3. element-ui之Message 详解
  4. 从零开发一款相机APP 第七篇: Camera2相机 预览功能实现
  5. 理解AttributeUsage类
  6. Unity中通过图片加载造成水纹波动的方法(附贴图材质球和脚本)
  7. 华为OD机试 - 租车骑绿岛(Python)
  8. The service already exists
  9. Spring的下载与配置
  10. mysql jdbctype long_mybatis常用jdbcType数据类型