转载自https://blog.51cto.com/12814931/2343623

近日接了一些oom案子,此类问题通常是客户自身业务导致的问题。但现在客户的提问越来越复杂,通常情况下我们需要站在客户一侧提供“协助”的技术服务。oom类案例通过一年多的学习和探讨,我将其分为3类:

1、内存真的不足

2、文件数到达上限

3、lowmem内存不足

内存真的不足的情况针对于64位系统,这时可能导致的原因有:进程hung住占用大量内存、进程申请连续大页导致溢出、本身内存负载接近临界值。这时可尝试升级内存配置、优化业务进程来解决。

单个进程最多允许打开的文件句柄数(包括socket连接数)是有限制的,当大于这个系统限制时,程序会抛出大量的无法打开文件的报错。这种情况设涉及到几个内核参数:

/proc/sys/fs/nr_open——系统文件系统支持文件句柄总数上限,默认值1048576(1M),Linux2.6.25开始增加该内核参数,用于替换内核宏NR_OPEN(1048576),该值上限受限于系统内存。

/proc/sys/fs/file-max——系统文件系统支持文件句柄总数最大值,必须小于/proc/sys/fs/nr_open或NR_OPEN,增加该值时,必须同步修改/proc/sys/fs/inode-max = 4*/proc/sys/fs/file-max。

因此当出现messages中存在相关句柄数达上限之类的提示时就可以适当调高上述内核参数

对于32位机器会出现一些内存使用率不高却仍然发生oom的情况,这是因为对于32位机器内核采用lowmem来管理highmem。LowMem 区 (也叫 NORMAL ZONE ) 一共 880 MB,而且不能改变(除非用 hugemem 内核)。对于高负载的系统,就可能因为 LowMem 利用不好而引发 OOM Killer 。一个可能原因是 LowFree 太少了,另外一个原因是 LowMem 里都是碎片,请求不到连续的内存区域。

检查当前 LowFree 的值:

# cat /proc/meminfo |grep LowFree

检查LowMem内存碎片:

# cat /proc/buddyinfo

这时可以通过升级到64位系统、使用hugemem内核(安装hugemem kernel RPM)、适当调高/proc/sys/vm/lower_zone_protection(lower_zone_protection越高,系统越倾向于保护lowmem)来解决。

oom的kill机制


如果oom_kill_allcating_task设置为非零值,则oom根据score_adj来选择杀掉的进程。 /proc/[pid]/oom_adj ,该pid进程被oom killer杀掉的权重,介于 [-17,15]之间,越高的权重,意味着更可能被oom killer选中,-17表示禁止被kill掉。关于kill机制,涉及到2个内核参数:

1、oom_kill_allocating_task

控制在OOM时是否杀死触发OOM的进程。

如果设置为0,OOM killer会扫描进程列表,选择一个进程来杀死。通常都会选择消耗内存内存最多的进程,杀死这样的进程后可以释放大量的内存。

如果设置为非零值,OOM killer只会简单地将触发OOM的进程杀死,避免遍历进程列表(代价比较大)。如果panic_on_oom被设置,则会忽略oom_kill_allocating_task的值。

默认值是0。

2、vm.panic_on_oom

控制内核在OOM发生时时是否panic。

如果设置为0,内核会杀死内存占用过多的进程。通常杀死内存占用最多的进程,系统就会恢复。

如果设置为1,在发生OOM时,内核会panic。然而,如果一个进程通过内存策略或进程绑定限制了可以使用的节点,并且这些节点的内存已经耗尽,oom-killer可能会杀死一个进程来释放内存。在这种情况下,内核不会panic,因为其他节点的内存可能还有空闲,这意味着整个系统的内存状况还没有处于崩溃状态。

如果设置为2,在发生OOM时总是会强制panic,即使在上面讨论的情况下也一样。即使在memory cgroup限制下发生的OOM,整个系统也会panic。

默认值是0。

将该参数设置为1或2,通常用于集群的故障切换。选择何种方式,取决于你的故障切换策略。

代码判断逻辑如下:

void out_of_memory(struct zonelist *zonelist, gfp_t gfp_mask,

int order, nodemask_t *nodemask, bool force_kill)

{

// 等待notifier调用链返回,如果有内存了则返回

blocking_notifier_call_chain(&oom_notify_list, 0, &freed);

if (freed > 0)

return;

// 如果进程即将退出,则表明可能会有内存可以使用了,返回

if (fatal_signal_pending(current) || current->flags & PF_EXITING) {

set_thread_flag(TIF_MEMDIE);

return;

}

// 如果设置了sysctl的panic_on_oom,则内核直接panic

check_panic_on_oom(constraint, gfp_mask, order, mpol_mask);

// 如果设置了oom_kill_allocating_task

// 则杀死正在申请内存的process

if (sysctl_oom_kill_allocating_task && current->mm &&

!oom_unkillable_task(current, NULL, nodemask) &&

current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) {

get_task_struct(current);

oom_kill_process(current, gfp_mask, order, 0, totalpages, NULL,

nodemask,

"Out of memory (oom_kill_allocating_task)");

goto out;

}

// 用select_bad_process()选择badness指

// 数(oom_score)最高的进程

p = select_bad_process(&points, totalpages, mpol_mask, force_kill);

if (!p) {

dump_header(NULL, gfp_mask, order, NULL, mpol_mask);

panic("Out of memory and no killable processes...\n");

}

if (p != (void *)-1UL) {

// 查看child process, 是否是要被killed,则直接影响当前这个parent进程

oom_kill_process(p, gfp_mask, order, points, totalpages, NULL,

nodemask, "Out of memory");

killed = 1;

}

out:

if (killed)

schedule_timeout_killable(1);

}

计算权值时会将是否为系统进程、进程RSS和swap内存占用考虑进去。当然也可自行设置:echo 17 > /proc/[pid]/oom_adj(不允许oom杀掉这个进程)

计算逻辑代码如下:

unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,

const nodemask_t *nodemask, unsigned long totalpages)

{

long points;

long adj;

// 内部判断是否是pid为1的initd进程,是否是kthread内核进程,是否是其他cgroup,如果是则跳过

if (oom_unkillable_task(p, memcg, nodemask))

return 0;

p = find_lock_task_mm(p);

if (!p)

return 0;

// 获得/proc/[pid]/oom_adj权值,如果是OOM_SCORE_ADJ_MIN则返回

adj = (long)p->signal->oom_score_adj;

if (adj == OOM_SCORE_ADJ_MIN) {

task_unlock(p);

return 0;

}

// 获得进程RSS和swap内存占用

points = get_mm_rss(p->mm) + p->mm->nr_ptes +

get_mm_counter(p->mm, MM_SWAPENTS);

task_unlock(p);

// 计算步骤如下,【计算逻辑比较简单,不赘述了】

if (has_capability_noaudit(p, CAP_SYS_ADMIN))

adj -= 30;

adj *= totalpages / 1000;

points += adj;

return points > 0 ? points : 1;

}

相关内核参数解析


dirty_background_bytes

当脏页所占的内存数量超过dirty_background_bytes时,内核的pdflush线程开始回写脏页。

dirty_background_ratio

默认值 :10

参数意义:当脏页所占的百分比(相对于所有可用内存,即空闲内存页+可回收内存页)达到dirty_background_ratio时内核的pdflush线程开始回写脏页。增大会使用更多内存用于缓冲,可以提高系统的读写性能。当需要持续、恒定的写入场合时,应该降低该数值。

注意:dirty_background_bytes参数和dirty_background_ratio参数是相对的,只能指定其中一个。当其中一个参数文件被写入时,会立即开始计算脏页限制,并且会将另一个参数的值清零。

dirty_bytes

当脏页所占的内存数量达到dirty_bytes时,执行磁盘写操作的进程自己开始回写脏数据。

注意:dirty_bytes参数和dirty_ratio参数是相对的,只能指定其中一个。当其中一个参数文件被写入时,会立即开始计算脏页限制,并且会将另一个参数的值清零

dirty_ratio

默认值:40

参数意义:当脏页所占的百分比(相对于所有可用内存,即空闲内存页+可回收内存页)达到dirty_ratio时,进程pdflush会自己开始回写脏数据。增大会使用更多系统内存用于缓冲,可以提高系统的读写性能。当需要持续、恒定的写入场合时,应该降低该数值。

dirty_expire_centisecs

默认值:2999参数意义:用来指定内存中数据是多长时间才算脏(dirty)数据。指定的值是按100算做一秒计算。只有当超过这个值后,才会触发内核进程pdflush将dirty数据写到磁盘。

dirty_writeback_centisecs

默认值:499这个参数会触发pdflush回写进程定期唤醒并将old数据写到磁盘。每次的唤醒的间隔,是以数字100算做1秒。如果将这项值设为500就相当5秒唤醒pdflush进程。如果将这项值设为0就表示完全禁止定期回写数据。

drop_caches

向/proc/sys/vm/drop_caches文件中写入数值可以使内核释放page cache,dentries和inodes缓存所占的内存。

只释放pagecache:

echo 1 > /proc/sys/vm/drop_caches

只释放dentries和inodes缓存:

echo 2 > /proc/sys/vm/drop_caches

释放pagecache、dentries和inodes缓存:

echo 3 > /proc/sys/vm/drop_caches

这个操作不是破坏性操作,脏的对象(比如脏页)不会被释放,因此要首先运行sync命令。

注:这个只能是手动释放

legacy_va_layout

该文件表示是否使用最新的32位共享内存mmap()系统调用,Linux支持的共享内存分配方式包括mmap(),Posix,System VIPC。

0,使用最新32位mmap()系统调用。

1,使用2.4内核提供的系统调用。

lowmem_reserve_ratio

保留的lowmem,3列分别为DMA/normal/HighMem

在有高端内存的机器上,从低端内存域给应用层进程分配内存是很危险的,因为这些内存可以通过mlock()系统锁定,或者变成不可用的swap空间。在有大量高端内存的机器上,缺少可以回收的低端内存是致命的。因此如果可以使用高端内存,Linux页面分配器不会使用低端内存。这意味着,内核会保护一定数量的低端内存,避免被用户空间锁定。

这个参数同样可以适用于16M的ISA DMA区域,如果可以使用低端或高端内存,则不会使用该区域。

lowmem_reserve_ratio参数决定了内核保护这些低端内存域的强度。预留的内存值和lowmem_reserve_ratio数组中的值是倒数关系,如果值是256,则代表1/256,即为0.39%的zone内存大小。如果想要预留更多页,应该设更小一点的值。

min_free_kbytes

这个参数用来指定强制Linux VM保留的内存区域的最小值,单位是kb。VM会使用这个参数的值来计算系统中每个低端内存域的watermark[WMARK_MIN]值。每个低端内存域都会根据这个参数保留一定数量的空闲内存页。

一部分少量的内存用来满足PF_MEMALLOC类型的内存分配请求。如果进程设置了PF_MEMALLOC标志,表示不能让这个进程分配内存失败,可以分配保留的内存。并不是所有进程都有的。kswapd、direct reclaim的process等在回收的时候会设置这个标志,因为回收的时候它们还要为自己分配一些内存。有了PF_MEMALLOC标志,它们就可以获得保留的低端内存。

如果设置的值小于1024KB,系统很容易崩溃,在负载较高时很容易死锁。如果设置的值太大,系统会经常OOM。

max_map_count

进程中内存映射区域的最大数量。在调用malloc,直接调用mmap和mprotect和加载共享库时会产生内存映射区域。虽然大多数程序需要的内存映射区域不超过1000个,但是特定的程序,特别是malloc调试器,可能需要很多,例如每次分配都会产生一到两个内存映射区域。默认值是65536。

mmap_min_addr

指定用户进程通过mmap可使用的最小虚拟内存地址,以避免其在低地址空间产生映射导致安全问题;如果非0,则不允许mmap到NULL页,而此功能可在出现NULL指针时调试Kernel;mmap用于将文件映射至内存;该设置意味着禁止用户进程访问low 4k地址空间

nr_pdflush_threads

当前pdfflush线程数量,为read-only。

oom_dump_tasks

如果启用,在内核执行OOM-killing时会打印系统内进程的信息(不包括内核线程),信息包括pid、uid、tgid、vm size、rss、nr_ptes,swapents,oom_score_adj和进程名称。这些信息可以帮助找出为什么OOM killer被执行,找到导致OOM的进程,以及了解为什么进程会被选中。

如果将参数置为0,不会打印系统内进程的信息。对于有数千个进程的大型系统来说,打印每个进程的内存状态信息并不可行。这些信息可能并不需要,因此不应该在OOM的情况下牺牲性能来打印这些信息。

如果设置为非零值,任何时候只要发生OOM killer,都会打印系统内进程的信息。

默认值是1(启用)。

OOM killer(Out-Of-Memory killer):监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程杀掉

oom_kill_allocating_task

控制在OOM时是否杀死触发OOM的进程。

如果设置为0,OOM killer会扫描进程列表,选择一个进程来杀死。通常都会选择消耗内存内存最多的进程,杀死这样的进程后可以释放大量的内存。

如果设置为非零值,OOM killer只会简单地将触发OOM的进程杀死,避免遍历进程列表(代价比较大)。如果panic_on_oom被设置,则会忽略oom_kill_allocating_task的值。

默认值是0。

panic_on_oom

控制内核在OOM发生时时是否panic。

如果设置为0,内核会杀死内存占用过多的进程。通常杀死内存占用最多的进程,系统就会恢复。

如果设置为1,在发生OOM时,内核会panic。然而,如果一个进程通过内存策略或进程绑定限制了可以使用的节点,并且这些节点的内存已经耗尽,oom-killer可能会杀死一个进程来释放内存。在这种情况下,内核不会panic,因为其他节点的内存可能还有空闲,这意味着整个系统的内存状况还没有处于崩溃状态。

如果设置为2,在发生OOM时总是会强制panic,即使在上面讨论的情况下也一样。即使在memory cgroup限制下发生的OOM,整个系统也会panic。

默认值是0。

将该参数设置为1或2,通常用于集群的故障切换。选择何种方式,取决于你的故障切换策略。

overcommit_memory

默认值为:0从内核文档里得知,该参数有三个值,分别是:0:当用户空间请求更多的的内存时,内核尝试估算出剩余可用的内存。

1:当设这个参数值为1时,内核允许超量使用内存直到用完为止,主要用于科学计算

2:当设这个参数值为2时,内核会使用一个决不过量使用内存的算法,即系统整个内存地址空间不能超过swap+50%的RAM值,50%参数的设定是在overcommit_ratio中设定。

overcommit_ratio

默认值为:50这个参数值只有在vm.overcommit_memory=2的情况下,这个参数才会生效。该值为物理内存比率,当overcommit_memory=2时,进程可使用的swap空间不可超过PM * overcommit_ratio/100

Swappiness

该参数控制是否使用swap分区,以及使用的比例。设置的值越大,内核会越倾向于使用swap。如果设置为0,内核只有在看空闲的和基于文件的内存页数量小于内存域的高水位线(应该指的是watermark[high])时才开始swap。

默认值是60。

vfs_cache_pressure

控制内核回收dentry和inode cache内存的倾向。

默认值是100,内核会根据pagecache和swapcache的回收情况,让dentry和inode cache的内存占用量保持在一个相对公平的百分比上。

减小vfs_cache_pressure会让内核更倾向于保留dentry和inode cache。当vfs_cache_pressure等于0,在内存紧张时,内核也不会回收dentry和inode cache,这容易导致OOM。如果vfs_cache_pressure的值超过100,内核会更倾向于回收dentry和inode cache。

优化的建议


echo 【调高】(针对32位) > /proc/sys/vm/lower_zone_protection

echo 【调低】 > /proc/sys/vm/min_free_kbytes

echo 【调高】 > /proc/sys/vm/dirty_ratio

echo 【调低】 > /proc/sys/vm/dirty_background_ratio

echo 【调低】> /proc/sys/vm/lowmem_reserve_ratio

echo 【调高】> /proc/sys/vm/swappiness

echo 【调高】> /proc/sys/fs/file-max

echo 2 > /proc/sys/vm/overcommit_memory

调高/etc/security/limits.conf连接数目

当然也可通过直接关闭oom规避,但是这样很危险,可能会导致系统发生crash

关闭/打开oom-killer:

# echo "0" > /proc/sys/vm/oom-kill

# echo "1" > /proc/sys/vm/oom-kill

转载自https://blog.51cto.com/12814931/2343623

oom机制分析及对应优化策略相关推荐

  1. inputstreamreader未关闭会导致oom_Linux内核OOM机制分析和防止进程被OOM杀死的方法...

    问题描述 Linux 内核有个机制叫 OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程 ...

  2. Chrome V8系列--浅析Chrome V8引擎中的垃圾回收机制和内存泄露优化策略

    V8 实现了准确式 GC,GC 算法采用了分代式垃圾回收机制.因此,V8 将内存(堆)分为新生代和老生代两部分. 一.前言 V8的垃圾回收机制:JavaScript使用垃圾回收机制来自动管理内存.垃圾 ...

  3. 逃逸分析--代码三大优化策略(堆不是分配对象存储的唯一选择)

    堆是分配对象存储的唯一选择吗? 随着JIT编译器的发展与逃逸分析逐渐成熟,栈上分配.标量替换优化技术将会导致一些微妙的变化,所以对象都分配到堆上也渐渐变得不是那么绝对了. 在Java虚拟机中,对象是在 ...

  4. Android 系统性能优化(43)---Android OOM案例分析

    Android OOM案例分析 在Android(Java)开发中,基本都会遇到java.lang.OutOfMemoryError(本文简称OOM),这种错误解决起来相对于一般的Exception或 ...

  5. 搜索引擎优化系统知名乐云seo_搜索引擎优化策略分析-乐云SEO

    搜索引擎优化策略分析-乐云SEO 类别:seo技术 来源:日期:2020-04-20 09:59:10人气值: Seo推广是一种将网站优化和网络运营媒体推广相结合的技术,现在它恰好是最流行的媒体.正因 ...

  6. 用科技词汇润色下句:并将模型在中国西北地区应用,对水碳能耦合循环过程进行定量评估和时空格局分析,旨在优化区域生态系统管理策略。...

    我们将在中国西北地区应用机器学习模型,对水碳能耦合循环进行定量评估和时空格局分析,以优化该地区的生态系统管理策略.

  7. 性能优化分析及常见性能优化策略总结

    最近,大家似乎都对性能优化分析,这一方面比较感兴趣.一方面是比较感兴趣,另一方面就是遇见许多类似的状况,但是,却不知从何下手,根源在哪里?应当如何正确优化?首先,先跟大家讲解下常见的性能优化策略分类! ...

  8. Linux内核OOM机制的详细分析

    Linux 内核有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程杀掉.典型的 ...

  9. Linux内核OOM机制的详细分析【转】

    本文转载自:http://blog.csdn.net/liukuan73/article/details/43238623 Linux内核根据应用程序的要求分配内存,通常来说应用程序分配了内存但是并没 ...

最新文章

  1. 将 TensorFlow 移植到 Android手机,实现物体识别、行人检测和图像风格迁移详细教程
  2. UA MATH523A 实分析1 集合论基础2 序关系与Zorn引理
  3. docker容器的基本操作
  4. libGDX-wiki发布
  5. linux之睡眠函数(my_sleep)
  6. 2022年1月国产数据库排行榜:TiDB霸榜两年势头不减,openGauss与OceanBase分数大涨...
  7. 【剑指offer】调整数组顺序使奇数位于偶数前面
  8. [导入]Gentoo版Linux操作系统的前世今生 (3)
  9. js入门笔记整理(二)——操作符
  10. vmware下安装rhel5
  11. AOP面向切面编程 淘宝京东网络处理
  12. java之家_java
  13. Minimum Window Substring @LeetCode
  14. LoaderManager使用具体解释(四)---实例:AppListLoader
  15. extmail mysql数据库 重启_一个简单的基于postfix+extmail+mysql的邮件系统
  16. 可逆矩阵性质总结_逆矩阵的定义与性质.doc
  17. eclipse jdt
  18. FPGA 20个例程篇:9.DDR3内存颗粒初始化写入并通过RS232读取(下)
  19. 百度地图分不同色块显示某个城市的行政区划,并添加城市(区)名
  20. 一点就分享系列(实践篇3-中篇)— 虽迟但到!全网首发?yolov5之“baseline修改小结“+“CV领域展开-Involutiontransformercnn”

热门文章

  1. 不做冤大头!大数据“杀熟”最高罚5000万!
  2. 【方案分享】2020娜扎X薇娅SKG娜小古直播方案.pdf(附下载链接)
  3. 速成pytorch学习——9天构建模型的3种方法
  4. 树莓派4b安装windows iot_树莓派4B基于python3安装opencv4全教程
  5. 20181031-1
  6. 8个Python高效数据分析的技巧
  7. git2.29.2..2安装_Centos6.5+jumpserver组件安装(2)
  8. 电脑功耗测试软件_进步超乎你想象!寻找移动处理器中的 最强性能 与 最低功耗...
  9. python 线性拟合 图_python线性拟合
  10. 推荐一款好用的消息推送服务WxPusher