一、Linux的虚拟内存管理有几个关键概念

1.每个进程有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址
2.虚拟地址可通过每个进程的页表与物理地址进行映射,获得真正物理地址
3.如果虚拟地址对应的物理地址不在物理内存中,则产生缺页中断,并真正分配物理地址,同时更新进程的页表;如果此时物理内存已耗尽,则根据内存替换算法淘汰部分页面至物理磁盘中。

二、Linux进程虚拟地址分布

Linux使用虚拟地址空间,大大增加了进程的寻址空间,由低地址到高地址分别为

只读段:该部分空间只能读,不可写。包括代码段、rodata段(C常量字符串和#define定义的常量)

数据段:保存全局变量、静态变量的空间

堆:就是平时所说的动态内存, malloc/new大部分都来源于此。其中堆顶的位置可通过函数brk和sbrk进行动态调整。

文件映射区域:如动态库、共享内存等映射物理空间的内存,一般是mmap函数所分配的虚拟地址空间。

栈:用于维护函数调用的上下文空间,一般为8M,可通过ulimit –s查看。

内核虚拟空间:用户代码不可见的内存区域,由内核管理。

32位系统有4G的地址空间,其中0x08048000~0xbfffffff 是用户空间,0xc0000000~0xffffffff是内核空间,包括内核代码和数据、与进程相关的数据结构(如页表、内核栈等)。

64位Linux一般使用48位来表示虚拟地址空间,40位表示物理地址,这可通过 /proc/cpuinfo 来查看

address sizes : 40 bits physical, 48 bits virtual

三、malloc是如何分配内存的?

malloc是glibc中内存分配函数,也是最常用的动态内存分配函数,其内存必须通过free进行释放,否则导致内存泄露。关于 malloc 获得虚存空间的实现,与glibc的版本有关,但大体逻辑是:

1.申请128k以内的内存,调用sbrk(),在堆内分配,将堆顶指针向高地址移动,获得新的虚存空间。虚拟地址比较小。

2.每次分配的内存地址前16字节(64位系统,32位是8字节)是记录该内存块的控制信息(用于 free )。

3.并不是每次malloc都会导致堆顶的增大,如果堆内有足够剩余空间,堆顶不会发生变化。

4.当分配大小大于128k,使用mmap在文件映射区域中分配匿名虚存空间获得地址空间,地址一般比较大(靠近栈区间)

5可通过函数mallopt(M_MMAP_THRESHOLD, 64*1024) 修改使用 mmap 的临界值为 64k ,后续大于 64k 的分配就会使用 mmap.

四、malloc分配多大的内存,就占用多大的物理内存空间吗?

malloc分配的的内存是虚拟地址空间

1.VSZ并不是每次malloc后都增长,因为可重用堆顶内剩余的空间,这样的malloc是很轻量快速的。

2.如果VSZ发生变化,基本与分配内存量相当,因为VSZ是计算虚拟地址空间总大小。

3.RSS的增量很少,是因为malloc分配的内存并不就马上分配实际存储空间,只有第一次使用,发现虚存对应的物理页面未分配,产生缺页中断,才真正分配物理页面,同时更新进程页面的映射关系(按需分配)。

4.由于每个物理内存页面大小是4k,不管memset其中的1k还是5k、7k,实际占用物理内存总是4k的倍数。所以RSS的增量总是4k的倍数。

五、如何查看进程虚拟地址空间的使用情况?

pmap

六、free的内存真的释放了?

使用mmap分配的内存会调用munmap系统调用来释放,并会真正释放该空间。

free释放内存,在glibc中,仅仅是标记为可用,形成一个内存空洞(碎片),并没有真正释放

glibc的free实现中,只要堆顶附近释放总空间(包括合并的空间)超过128k,即会调用sbrk(-SIZE)来回溯堆顶指针,将原堆顶空间还给OS。否则都成为碎片(碎片如果相邻会适当合并)

七、程序代码中malloc的内存都有相应的free,就不会出现内存泄露了吗?

随着系统频繁地malloc和free,尤其对于小块内存,堆内将产生越来越多不可用的碎片,导致“内存泄露”。而这种“泄露”现象使用valgrind是无法检测出来的。

八、既然堆内内存不能直接释放,为什么不全部使用mmap来分配?

使用mmap分配1M空间,第一次调用产生了大量缺页中断(1M/4K次)。缺页中断是内核行为,会导致内核态CPU消耗较大。

堆是一个连续空间,并且堆内碎片由于没有归还OS,如果可重用碎片,再次访问该内存很可能不需产生任何系统调用和缺页中断,这将大大降低CPU的消耗。

九、如何查看进程的缺页中断信息?

ps -omajflt,minflt -C

ps -omajflt,minflt -p

其中majflt表major fault,指大错误。mnflt代表minor fault,指小错误。这两个数值表示一个进程自启动以来所发生的缺页中断的次数。其中majflt与minflt的不同是,majflt表示需要读写磁盘,可能是内存对应页面在磁盘中需要load到物理内存中,也可能是此时物理内存不足,需要淘汰部分物理页面至磁盘中

如果进程的内核态CPU使用过多,其中一个原因就可能是单位时间的缺页中断次数多个,可通过以上命令来查看。

如果MAJFLT过大,很可能是内存不足。

如果MINFLT过大,很可能是频繁分配/释放大块内存(128k),malloc使用mmap来分配。对于这种情况,可通过mallopt(M_MMAP_THRESHOLD, )增大临界值,或程序实现内存池。

十、如何查看堆内内存的碎片情况

可通过mallinfo结构中的fsmblks、smblks、ordblks值得到,这些值表示不同大小区间的碎片总个数,这些区间分别是0~80字节,80~512字节,512~128k。如果fsmblks、 smblks的值过大,那碎片问题可能比较严重了

十一、除了glibc的malloc/free,还有其他第三方实现吗?

google的tcmalloc和facebook的jemalloc