Linux 内存

# cat /var/log/messages

Out of memory: Killed process xxxxx, UID xx, (httpd).

httpd invoked oom-killer:

klogd invoked oom-killer

OOM(Out Of Memory,内存不足)

如果某个程序发生了内存泄漏,那么一般情况下系统就会试用OOM机制将其进程Kill掉。

-------------------------------------------------------------------------

OOM

Linux下允许程序申请比系统可用内存更多的内存,这个特性叫Overcommit。这样做是出于优化系统考虑,因为不是所有的程序申请了内存就立刻使用的,当你使用的时候说不定系统已经回收了一些资源了。不幸的是,当你用到这个Overcommit给你的内存的时候,系统还没有资源的话,OOM killer就跳出来了。

Linux下有3种Overcommit的策略(参考内核文档:vm/overcommit-accounting),可以在/proc/sys/vm/overcommit_memory配置。取0,1和2三个值,默认是0。

0:启发式策略,比较严重的Overcommit将不能得逞,比如你突然申请了128TB的内存。而轻微的Overcommit将被允许。另外,root能Overcommit的值比普通用户要稍微多些。

1:永远允许Overcommit,这种策略适合那些不能承受内存分配失败的应用,比如某些科学计算应用。

2:永远禁止Overcommit,在这个情况下,系统所能分配的内存不会超过swap+RAM*系数(/proc/sys/vm/overcmmit_ratio,默认50%,你可以调整),如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序。

实际上启发策略只有在启用了SMACK或者SELinux模块时才会起作用,其他情况下等于永远允许策略。

选择进程的策略
好了,只要存在Overcommit,就可能会有OOM killer跳出来。那么OOM killer跳出来之后选目标的策略又是什么呢?我们期望的是:没用的且耗内存多的程序被枪。

Linux下这个选择策略也一直在不断的演化。作为用户,我们可以通过设置一些值来影响OOM killer做出决策。Linux下每个进程都有个OOM权重,在/proc/<pid>/oom_adj里面,取值是-17到+15,取值越高,越容易被干掉。

最终OOM killer是通过/proc/<pid>/oom_score这个值来决定哪个进程被干掉的。这个值是系统综合进程的内存消耗量、CPU时间(utime + stime)、存活时间(uptime - start time)和oom_adj计算出的,消耗内存越多分越高,存活时间越长分越低。
Linux在计算进程的内存消耗的时候,会将子进程所耗内存的一半同时算到父进程中。

------------------------------------------------------------------------------

在 32 位CPU 架构下寻址是有限制的。Linux 内核定义了三个区域:

--------------------------------------------------------------------------------

# DMA: 0x00000000 - 0x00999999 (0 - 16 MB)
# LowMem: 0x01000000 - 0x037999999 (16 - 896 MB) - size: 880MB
# HighMem: 0x038000000 - <硬件特定>

---------------------------------------------------------------------------------------
LowMem 区 (也叫 NORMAL ZONE ) 一共 880 MB,而且不能改变(除非用 hugemem 内核)。对于高负载的系统,就可能因为 LowMem 利用不好而引发 OOM Killer 。一个可能原因是 LowFree 太少了,另外一个原因是 LowMem 里都是碎片,请求不到连续的内存区域

对于某个用户页面的请求可以首先从“普通”区域中来满足(ZONE_NORMAL);
如果失败,就从ZONE_HIGHMEM开始尝试;

如果这也失败了,就从ZONE_DMA开始尝试。

这种分配的区域列表依次包括ZONE_NORMAL、ZONE_HIGHMEM和ZONE_DMA区域。

另一方面,对于 DMA 页的请求可能只能从 DMA 区域中得到满足,因此这种请求的区域列表就只包含 DMA 区域

检查当前 low&hight 内存 的值

# egrep 'High|Low' /proc/meminfo
或者
# free -lm

检查LowMem内存碎片

# cat /proc/buddyinfo

-------------------------------------------------------------------------------------------------------

Node 0, zone DMA 24 25 13 639 155 0 1 0 1 1 0

内存物理上使用基本没有所谓的碎片问题.读写内存上任何一个字节的速度都是固定的。不存在硬盘的机械寻道慢的问题。

buddy system分配内存的时候并不能保证物理地址连续的. 尤其是非2的整数次冥的页面数量的时候.需要mmu将这些物理地址不连续的内存块在线性空间上拼接起来.

node0 就是有几个内存结点 如果不是NUMA那就是只有一个了
zone 内存区域

DMA 内存开始的16 MB

HighMem 所有大于4G的内存 ,

Normal 介于两者之间的内存

后面11个数字是以4,8,16,32,64,128,256,512,1024,2048,4096kb为单位的所有可分配内存

24*4+25*8+13*16+639*32+155*64+0*128+1*256+0*512+1*1024+1*2048+0*4096(kb)就是可用的内存(可以连续分配的内存地址空间)

--------------------------------------------------------------------------

查看 low&hight 内存的状态
# egrep 'High|Low' /proc/meminfo
或者
# free -lm

当low 内存不足时,不管high 内存有多大,oom-killer都会开始结束一些进程保持系统运行。oom的一个原因就是虽然看着有内存,但是无法分配,所以Kill占用内存最大的程序。那都是进程级的,有问题自己解决,用内存池也行。系统都是大块分配给进程并回收,都是整页的,不管多离散,在虚存空间都能映射成连续的,所以没有碎片问题。

检查内存泄漏的工具
# valgrind

OOM Killer 在2.6 是杀掉占用内存最厉害的进程(很容易导致系统应用瘫痪)

OOM问题的解决方法

------------------------------------------------------------------------------

2.6新增了一个参数: vm.lower_zone_protection 。这个参数默认的单位为 MB,默认 LowMem 为 16MB。建议设置 vm.lower_zone_protection = 200 甚至更大以避免 LowMem 区域的碎片,是绝对能解决这个问题的(这参数就是解决这个问题出来的)。

OOM Killer 的关闭与激活方式:

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

启动时生效:

# vi /etc/sysctl.conf:
vm.oom-kill = 0

vm.lower_zone_protection = 300

(为low memory 保护性分配300M)

---------------------------------------------------------------------------------------------------------

内核内存管理

Linux采用“按需调页”算法,支持三层页式存储管理策略。将每个用户进程4GB长度的虚拟内存划分成固定大小的页面。其中0至3GB是用户态空间,由各进程独占;3GB到4GB是内核态空间,由所有进程共享,但只有内核态进程才能访问。

Linux将物理内存也划分成固定大小的页面,由数据结构page管理,有多少页面就有多少page结构,它们又作为元素组成一个数组mem_map[]。

linux低层采用三层结构,实际使用中可以方便映射到两层或者三层结构,以适用不同的硬件结构。最下层的申请内存函数get_free_page。之上有三种类型的内存分配函数
(1)kmalloc类型。内核进程使用,基于slab技术,用于管理小于内存页的内存申请。思想出发点和应用层面的内存缓冲池同出一辙。但它针对内核结构,特别处理,应用场景固定,不考虑释放。不再深入探讨。
(2)vmalloc类型。内核进程使用。用于申请不连续内存。
(3)brk/mmap类型。用户进程使用。malloc/free实现的基础。

malloc系统的内存管理策略
malloc系统有自己的内存池管理策略,malloc的时候,检测池中是否有足够内存,有则直接分配,无则从内存中调用brk/mmap函数分配,一般小于等于128k(可设置)的内存,使用brk函数,此时堆向上(有人有的硬件或系统向下)增长,大于128k的内存使用mmap函数申请,此时堆的位置任意,无固定增长方向。free的时候,检测标记是否是mmap申请,是则调用unmmap归还给操作系统,非则检测堆顶是否有大于128k的空间,有则通过brk归还给操作系统,无则标记未使用,仍在glibc的管理下。glibc为申请的内存存储多余的结构用于管理,因此即使是malloc(0),也会申请出内存(一般16字节,依赖于malloc的实现方式),在应用程序层面,malloc(0)申请出的内存大小是0,因为malloc返回的时候在实际的内存地址上加了16个字节偏移,而c99标准则规定malloc(0)的返回行为未定义。除了内存块头域,malloc系统还有红黑树结构保存内存块信息,不同的实现又有不同的分配策略。频繁直接调用malloc,会增加内存碎片,增加和内核态交互的可能性,降低系统性能。linux下的glibc多为Doug Lea实现

slab:在操作系统的运作过程中,经常会涉及到大量对象的重复生成、使用和释放问题。对象生成算法的改进,可以在很大程度上提高整个系统的性能。在Linux系统中所用到的对象,比较典型的例子是inode、task_struct等,都又这些特点。一般说来,这类对象的种类相对稳定,每种对象的数量却是巨大的,并且在初始化与析构时要做大量的工作,所占用的时间远远超过内存分配的时间。但是这些对象往往具有这样一个性质,即他们在生成时,所包括的成员属性值一般都赋成确定的数值,并且在使用完毕,释放结构前,这些属性又恢复为未使用前的状态。因此,如果我们能够用合适的方法使得在对象前后两次背使用时,在同一块内存,或同一类内存空间,且保留了基本的数据结构,就可以大大提高效率。slab算法就是针对上述特点设计的。

slab算法思路中最基本的一点被称为object-caching,即对象缓存。其核心做法就是保留对象初始化状态的不变部分,这样对象就用不着在每次使用时重新初始化(构造)及破坏(析构)。

面向对象的slab分配中有如下几个术语:

l 缓冲区(cache):一种对象的所有实例都存在同一个缓存区中。不同的对象,即使大小相同,也放在不同的缓存区内。每个缓存区有若干个slab,按照满,半满,空的顺序排列。在slab分配的思想中,整个内核态内存块可以看作是按照这种缓存区来组织的,对每一种对象使用一种缓存区,缓存区的管理者记录了这个缓存区中对象的大小,性质,每个slab块中对象的个数以及每个slab块大小。

l slab块:slab块是内核内存分配与页面级分配的接口。每个slab块的大小都是页面大小的整数倍,有若干个对象组成。slab块共分为三类:

完全块:没有空闲对象。

部分块:只分配了部分对象空间,仍有空闲对象。

空闲块:没有分配对象,即整个块内对象空间均可分配。

在申请新的对象空间时,如果缓冲区中存在部分块,那么首先查看部分块寻找空闲对象空间,若未成功再查看空闲块,如果还未成功则为这个对象分配一块新的slab块。

l 对象:将被申请的空间视为对象,使用构造函数初始化对象,然后由用户使用对象。

Linux下Glibc的内存管理机制大致如下:

从操作系统的角度看,进程的内存分配由两个系统调用完成:brk和mmap。brk是将数据段(.data)的最高地址指针_edata往高地址推,mmap是在进程的虚拟地址空间中找一块空闲的。其中,mmap分配的内存由munmap释放,内存释放时将立即归还操作系统;而brk分配的内存需要等到高地址内存释放以后才能释放。也就是说,如果先后通过brk申请了A和B两块内存,在B释放之前,A是不可能释放的,仍然被进程占用,通过TOP查看疑似”内存泄露”。默认情况下,大于等于128KB的内存分配会调用mmap/mummap,小于128KB的内存请求调用sbrk(可以通过设置M_MMAP_THRESHOLD来调整)。

Glibc的新特性:M_MMAP_THRESHOLD可以动态调整。M_MMAP_THRESHOLD的值在128KB到32MB(32位机)或者64MB(64位机)之间动态调整,每次申请并释放一个大小为2MB的内存后,M_MMAP_THRESHOLD的值被调整为2M到2M + 4K之间的一个值

M_MMAP_THRESHOLD的值动态调整,后续的2MB的内存申请通过sbrk实现,而sbrk需要等到高地址内存释放以后低地址内存才能释放。可以通过显式设置M_MMAP_THRESHOLD或者M_MMAP_MAX来关闭M_MMAP_THRESHOLD动态调整的特性,从而避免上述问题。

当然,mmap调用是会导致进程产生缺页中断的,为了提高性能,常见的做法如下:

1, 将动态内存改为静态,比如采用内存池技术或者启动的时候给每个线程分配一定大小,比如8MB的内存,以后直接使用;

2, 禁止mmap内存调用,禁止Glibc内存缩紧将内存归还系统,Glibc相当于实现了一个内存池功能。

---------------------------------------------------------------------

内存碎片

碎片就是,一个50k的内存,你先吃20k,然后再吃5k,然后释放掉先前吃的那20k,剩下的内存布局就是 {20k 空闲}{5k 占用}{25k 空闲},这样虽然总计有45k空闲,但是malloc(30)都会得不到,因为没有连续的30k空闲内存。
但无论是什么内存碎片整理工具都是无法将那5K往前挪动。
理由是
如果你程序用一个 void *p 指针指向那5K的内存地址,假如用外部的整理工具挪动那5K内存,就需要改动void *p的指向,然而外部的整理工具是无法知道你程序中有哪些指针,或者指针偏移是指向那块内存。

还有一种碎片叫"内碎片"{20k 空闲}{25k 空闲},这种内碎片,这种碎片不必去理会,那是相对于操作系统管理上的碎片,这种碎片对于系统内核是碎片,但对于程序来说不是碎片,程序可以正常使用这样的内存。

解决办法是加载一个内存池,如果程序已经成型,不能往原程序里面加内存池,那还有一个办法
glibc有一个机制,用环境变量LD_PRELOAD可以替换掉一个lib。
堆碎片是由于malloc 和 free函数产生。你就弄一个带内存池的malloc和free,然后LD_PRELOAD替换掉glibc的malloc和free
这样只是把程序封闭起来,防止别的程序对他的碎片干扰。如果程序自身会产生碎片,这方法就无效了。
你还想处理掉自身碎片,那就用CPU陷阱技术

正考虑能不能不用内存池来解决,比如用多进程来处理数据
要处理复杂数据的地方先fork,在子进程里处理,然后通过shm等方式把结果传回来,然后子进程退出,退出时产生的碎片一并清除...这样来看,多进程方式的程序要优于多线程,能够轻松地把内存碎片阿,崩溃阿,推给子进程

linux的确不会出现系统级内存碎片和内存泄漏

Linux 对每个进程都是大块的提供内存,进程中malloc只在块中分配,不够了可以再要一大块。进程结束时,整块归还。
因此即使进程里有内存泄漏,结束时也都释放了。

linux采用什么分配方式、算法避免内存碎片的?

进程上是分配一个heap区,大约几M,并在PCB中有一个内存链表。
进程malloc是就在heap区进行,如果不够了,用brk系统调用申请新的块加入到进程的heap区,这些块都在PCB资源链表中,进程结束时(不管什么原因),系统都会回收所有资源。
所以OS没有内存就是真没有了,不存在碎片和丢失问题。

------------------------------------------------------------------------------------

SWAP

内存不足,触发oom(out of memory)机制,导致部分服务被迫终止。

kswapd0:是虚拟内存管理中, 负责换页的,如果其它的进程缺少内存可用, 于是kswapd0出来, 执行换页操作。在kswapd中,有2个阀值,pages_hige和pages_low,当空闲内存页的数量低于pages_low的时候,kswapd进程就会扫描内存并且每次释放出32个free pages,直到free page的数量到达pages_high。

kjournald:EXT3文件系统的日志进程,具有3种模式:1.journal---记录所有文件系统上的元数据改变,最慢的一种模式。2.ordered---默认使用的模式,只记录文件系统改变的元数据,并在改变之前记录日志。3.writeback---最快的一种模式,同样只记录修改过的元数据,依赖标准文件系统写进程将数据写到硬盘。

修改ext3工作模式:

# vi /etc/fstab
/dev/hda5 /opt ext3 data=writeback 1 0

第1列是需要挂载的文件系统或存储设备,也可以是远程的文件系统。
第2列是挂载点。挂载点,也就是自己找一个或创建一个dir(目录),然后把文件系统<fie sysytem>挂到这个目录上,然后就可以从这个目录中

访问要挂载文件系统。
第3列指定文件系统或分区的类型,下面的文件系统都是目前Linux所能支持的:adfs、befs、cifs、ext3、 ext2、ext、iso9660、kafs、minix、

msdos、vfat、umsdos、proc、reiserfs、swap、 squashfs、nfs、hpfs、ncpfs、ntfs、affs、ufs。
第4列为挂载选项,详细参考man mount. 下面列出一些常用的选项:
auto: 系统自动挂载,fstab默认就是这个选项
ro: read-only
rw: read-write
defaults: rw, suid, dev, exec, auto, nouser, and async.

第5列为dump选项,设置是否让备份程序dump备份文件系统,0为忽略,1为备份。
第6列为fsck选项,告诉fsck程序以什么顺序检查文件系统,如果这里填0,则不检查;挂载点为 / 的(即根分区),必须在这里填写1,其他的都不能填写1。如果有分区填写大于1的话,则在检查完根分区后,接着按填写的数字从小到大依次检查下去。同数字 的同时检查。比如第一和第二个分区填写2,第三和第四个分区填写3,则系统在检查完根分区后,接着同时检查第一和第二个分区,然后再同时检查第三和第四个 分区。

增加SWAP

1、检查当前的分区情况:

#free -m

2、增加交换分区文件及大小,如果要增加2G大小的交换分区,则命令写法如下,其中的 count 等于想要的块大小。

# dd if=/dev/zero of=/home/swap bs=1024 count=2048000

3、设置交换文件:

# mkswap /home/swap

4、立即启用交换分区文件

# swapon /home/swap

5、如果要在引导时自动启用,则编辑 /etc/fstab 文件,添加行:
/home/swap swap swap defaults 0 0

系统下次引导时,它就会启用新建的交换文件,再查看SWAP分区大小发现增加了2G。

---------------------------------------------------------------------------------------

虚拟内存

由于 x86 32 位 CPU ,它使用 32 位数来表示任意惟一的内存地址。这就意味着 x86 体系结构能够表示 2^32个惟一的内存地址(4,294,967,296,或者说4G)。这 4G 地址空间被 Linux 内核分成了物理地址和虚拟地址。 默认的划分是 1:3(1GB 物理,3GB 虚拟)。这就使 Linux 为每个进程提供 3GB 的虚拟地址空间,但它仅允许 1GB 的物理 RAM。

因为操作系统将内存都映射为虚拟内存,所以操作系统的物理内存结构对用户和应用来说通常都是不可见的,应用程序并不分配物理内存,而是向Linux内核请求一部分映射为虚拟内存的内存空间。
但虚拟内存并不一定是映射物理内存中的空间,如果应用程序有一个大容量的请求,也可能会被映射到在磁盘子系统中的swap空间中。
此种方法被称为Swap usage 如下图:

当swap分区使用超过50%时,并不意味着物理内存的使用已经达到瓶颈了,swap空间只是Linux内核更好的使用系统资源的一种方法。

简单理解:Swap usage只表示了Linux管理内存的有效性。对识别内存瓶颈来说,Swap In/Out才是一个比较又意义的依据(vmstat输出),如果Swap In/Out的值长期保持在每秒200到300个页面通常就表示系统可能存在内存的瓶颈

-------------------------------------------------------------------------------------------

参考:

linux有查看和整理内存碎片的工具吗?

http://bbs.chinaunix.net/viewthread.php?tid=1155588&extra=&page=6

OOM killer "Out of Memory: Killed process" SOLUTIONS / SUMMARY

http://www.redaht.com/archives/redhat-list/2007-August/msg00060.html

Linux下OOM Killer机制详解

http://blog.dccmx.com/2011/04/oom-killer-on-linux/

增加swap

http://www.cnblogs.com/ovliverlin/archive/2007/07/23/828671.html

Linux内核内存池管理技术实现分析
http://blog.csdn.net/danforn/article/details/1720791

内存池技术学习笔记
http://www.cppblog.com/bangle/archive/2008/08/26/59915.aspx

GLIBC内存分配机制引发的“内存泄露”

http://www.nosqlnotes.net/archives/105


转自:http://wlservers.blog.163.com/blog/static/120622304201183093227913/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值