当Linux用尽内存

欢迎进入Linux社区论坛,与200万技术人员互动交流 >>进入

  在OOM情况下,ptmalloc如何释放内存是很有趣的。使用mmap()分配的块通过unmap()释放之后就完全释放了,使用brk()分配的块是 做释放标记,但是他们仍在分配器控制之下。如果另外一个malloc()请求尺寸小于等于自由chunk.分配器可以把多个连续的自由chunk合并,也 可以把它分割来满足要求。

  这也就是说,一个自由chunk可能因为不能用来满足请求而被丢弃。失败的自由chunk合并也会加速OOM的产生。这也是糟糕的内存碎片的标志。

  恢复一旦发生了OOM,怎么办?内核会终止一个进程。为什么?这是唯一终止进一步请求内存的方法。内核不会假设进程有什么机制能自动终止,唯一的选择就是杀掉。

  内核怎么知道改杀谁呢?答案在mm/oom_kill.c源码中。这个所谓的OOM杀手用函数badness()衡量现有进程的得分。得分最高的就是受害者。以下是评分标准:VM尺寸。这不是所有分配页的尺寸,而是进程拥有的所有VMA的总量。尺寸越大得分越高。

  和一有关,子进程的VM尺寸也很重要。这个计数是累积的。

  进程优先级小于0的(nice过的)得分高。

  超级用户的进程被假设更重要,因而得分低。

  进程运行时。时间越长得分越低。

  进程进行直接硬件访问的可以免疫。

  swapper和init以及其他内核线程都免疫。

  进程得分最高的赢得选举,然后被杀。

  这个机制不完美,但是基本有效。标准一和二非常明确的表明VMA的尺寸的重要性,而不是实际页的数量。你可能觉得VMA尺寸也许会导致假警报,但是 其实不会。badness()调用发生在页分配函数中,当只有少数自由页而回收失败时,所以基本上这个值很接近进程拥有的页数。

  为什么不数实际的页数呢?因为这样需要更多时间和更多锁,也导致快速判断的开销增大。所以OOM并不完美,也可能杀错。

  内核使用SIGTERM信号通知目标进程关闭。

  如何降低OOM风险简单的规则:不要分配超出实际空闲的内存。然而,有很多因素会影响结果,所以策略要更精细一点儿:通过有序的分配减少碎片不需要高级的分配器。你可以通过有序的分配和释放减少碎片。使用LIFO策略:最后分配的最先释放。

  比如以下代码:void *a;void *b;void *c;…………

  a = malloc(1024);b = malloc(5678);c = malloc(4096);

  …………………。

  free(b);b = malloc(12345);可以换成:a = malloc(1024);c = malloc(4096);b = malloc(5678);…………………。

  free(b);b = malloc(12345);这样,a 和c 两个chunk之间就不会有漏洞。你也可以考虑使用realloc()来调整已经产生的malloc()块的尺寸。

  两个示例演示了这个影响。程序结束时会报告系统分配的内存字节数(内核和glibc分配器)以及实际使用的数量。例如,在2.6.11.1内核和glibc2.3.3.27上,不用参数fragmented1浪费了319858832 字节(约 305 MB) 而fragmented2 浪费了 2089200 字节 (越 2MB)。152倍!

  你可以进一步实验传递各种参数的结果。参数是malloc()的请求尺寸。

  调整内核的overcommit行为You can change the behavior of the Linux kernel through the /proc filesystem, as documented inDocumentation/vm/overcommit-accounting in the Linux kernel‘s source code. You have three choices when tuning kernel overcommit, expressed as numbers in /proc/sys/vm/overcommit_memory:你可以根据Documentation/vm/overcommit-accounting通过/proc目录的配置改变linux内核的行为。有三个选择:0意味着使用默认的模式判断是否overcommit. 1意味着总是overcommit. 你现在应该知道有多危险了。

  2防止过度overcommit.可以调整/proc/sys/vm/overcommit_ratio. 最大的承诺值是swap + overcommit_ratio*MEM.一般默认就够用了,但是模式2有更好的保护。相应的,模式2也需要你小心估计程序的需求。你肯定不想程序因为这个不能执行。当然这样也可以避免出现被杀掉。

  分配内存后检查NULL指针,审计内存泄露这是个简单的规则,但是容易被忽略掉。检查NULL可以知道分配器能够扩展内存区域,虽然不保证能分配需要的页。一般你需要担保或者推后分配,取决于情况。和overcommit配合, malloc()会因为认为不能申请自由页而返回NULL,从而避免了OOM.内存泄露是不必要的内存消耗。应用程序将不再追踪泄露的内存块但是内核也不会回收,因为内核认为程序还在用。valgrind可以用来追踪这一现象。

  总是查询内存分配统计linux内核提供了/proc/meminfo来找到内存状态信息。top free vmstat的信息皆来于此。

  你需要检查的是自由的和可回收的内存。自由不用解释,但什么是可回收的?这是指buffer和页cache.当内存紧张系统可以写回磁盘来回收。

  $ cat /proc/meminfo MemTotal: 255944 kB MemFree: 3668 kB Buffers: 13640 kB Cached: 171788 kB SwapCached: 0 kB HighTotal: 0 kB HighFree: 0 kB LowTotal: 255944 kB LowFree: 3668 kB SwapTotal: 909676 kB SwapFree: 909676 kB基于以上输出,自由的虚拟内存为MemFree + Buffers + Cached + SwapFree I failed to find any formalized C (glibc) function to find out free (including reclaimable) memory space. The closest I found is by using get_avphys_pages() or sysconf() (with the_SC_AVPHYS_PAGES parameter)。 They only report the amount of free memory, not the free + reclaimable amount.我不能找到一个正式的C函数来找出自由(含可回收)内存的空间。最接近的是get_avphys_pages() 或者 sysconf() (加 _SC_AVPHYS_PAGES 参数)他们只报告自由内存总量而不是自由加可回收。

  这意味着为了精确的信息,你需要自己解析/proc/meminfo并计算。如果你懒,可以参考procps源代码。它包含ps top free工具。

  关于其他内存分配器的实验不同的分配器使用不同方法管理内存chunk.Hoard是一个例子。Emery Berger from the University of Massachusetts用它来进行高性能的内存分配。用于多线程程序,引入了每CPU heap的概念。

  使用64位平台需要使用更大用户地址空间的人可以考虑64位计算。内核不再使用3:1方式分割VM,因而对大于4G内存的机器也很合适这个和扩展地址无关,比如INTEL的PAE,允许32位的处理器定址64G内存。这个是物理地址定址,跟用户无关。在虚拟地址部分用户仍然使用3GB.多余的内存可以访问,但是不是都可以映射到地址空间。不能映射的部分就不可用。

  考虑在结构中使用打包的类型Packed attributes can help to squeeze the size of structs, enums, and unions. This is a way to save more bytes, especially for array of structs. Here is a declaration example:打包的属性可以压缩struct enum 和 union的尺寸。这样对struct尤其可以节省struct test { char a;long b;} __attribute__ ((packed));这个招数在于它使各行不对齐,因而消耗了更多的CPU周期。对齐意味着变量的地址是数据类型的原本地址的整数倍。基于数据的访问频率,这样会更慢,但是考虑到排序和缓冲的相关性。

  在用户进程使用ulimit()

  使用ulimit -v可以限制用户能mmap()的内存地址空间。到上限后,mmap(),以及malloc()会返回0因而OOM不会启动。对于多用户系统很有用,因为避免了乱杀无辜。

[1][2][3]

天有泪,烛有泪,天泪有声,烛泪有形,

当Linux用尽内存

相关文章:

你感兴趣的文章:

标签云: