linux malloc内存申请相关参数设置

情况一、malloc小于128k的内存

malloc小于128k的内存时使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系),如下图:

1、进程启动的时候,其(虚拟)内存空间的初始布局如图1所示。

其中,mmap内存映射文件是在堆和栈的中间(例如libc-2.2.93.so,其它数据文件等),为了简单起见,省略了内存映射文件。
_edata指针(glibc里面定义)指向数据段的最高地址。

2、进程调用A=malloc(30K)以后,内存空间如图2:
malloc函数会调用brk系统调用,将_edata指针往高地址推30K,就完成虚拟内存分配。
你可能会问:只要把_edata+30K就完成内存分配了?

事实是这样的,_edata+30K只是完成虚拟地址的分配,A这块内存现在还是没有物理页与之对应的,等到进程第一次读写A这块内存的时候,发生缺页中断,这个时候,内核才分配A这块内存对应的物理页。也就是说,如果用malloc分配了A这块内容,然后从来不访问它,那么,A对应的物理页是不会被分配的。

3、进程调用B=malloc(40K)以后,内存空间如图3。

在这里插入图片描述

情况二、malloc大于128k的内存

malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,而且初始化为0),如下图:

在这里插入图片描述
4、进程调用C=malloc(200K)以后,内存空间如图4:
默认情况下,malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不是去推_edata指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存。
这样子做主要是因为:
brk分配的内存需要等到高地址内存释放以后才能释放(例如,在B释放之前,A是不可能释放的,这就是内存碎片产生的原因,什么时候紧缩看下面),而mmap分配的内存可以单独释放。
当然,还有其它的好处,也有坏处,再具体下去,有兴趣的同学可以去看glibc里面malloc的代码了。

5 、进程调用D=malloc(100K)以后,内存空间如图5;
6 、进程调用free©以后,C对应的虚拟内存和物理内存一起释放。
7、进程调用free(B)以后,如图7所示:
B对应的虚拟内存和物理内存都没有释放,因为只有一个_edata指针,如果往回推,那么D这块内存怎么办呢?

当然,B这块内存,是可以重用的,如果这个时候再来一个40K的请求,那么malloc很可能就把B这块内存返回回去了。

8、进程调用free(D)以后,如图8所示:
B和D连接起来,变成一块140K的空闲内存。

9、默认情况下:
当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。在上一个步骤free的时候,发现最高地址空闲内存超过128K,于是内存紧缩,变成图9所示。

内存相关参数设置接口

mallopt函数可以控制 内存分配的函数:

int mallopt(int param,int value)//控制 内存分配的函数 。

param 的取值可以为M_CHECK_ACTION、M_MMAP_MAX、M_MMAP_THRESHOLD、M_MXFAST(从glibc2.3起)、M_PERTURB(从glibc2.4起)、M_TOP_PAD、M_TRIM_THRESHOLD。

此处解释param取值为M_MXFAST的情况;

value是以 字节为单位的。

比如设置M_MMAP_THRESHOLD选项可以设置启用mmap申请malloc字节数阀值,设置-1是不启用mmap
这些选项可以通过mallopt()进行设置:

1. M_MXFAST
M_MXFAST用于设置fast bins中保存的chunk的最大大小,默认值为64B,fast bins中保存的chunk在一段时间内不会被合并,分配小对象时可以首先查找fast bins,如果fast bins找到了所需大小的chunk,就直接返回该chunk,大大提高小对象的分配速度,但这个值设置得过大,会导致大量内存碎片,并且会导致ptmalloc缓存了大量空闲内存,去不能归还给操作系统,导致内存暴增。

M_MXFAST的最大值为80B,不能设置比80B更大的值,因为设置为更大的值并不能提高分配的速度。Fast bins是为需要分配许多小对象的程序设计的,比如需要分配许多小struct,小对象,小的string等等。

如果设置该选项为0,就会不使用fast bins。

2. M_TRIM_THRESHOLD
M_TRIM_THRESHOLD用于设置mmap收缩阈值,默认值为128KB。自动收缩只会在free时才发生,如果当前free的chunk大小加上前后能合并chunk的大小大于64KB,并且top chunk的大小达到mmap收缩阈值,对于主分配区,调用malloc_trim()返回一部分内存给操作系统,对于非主分配区,调用heap_trim()返回一部分内存给操作系统,在发生内存收缩时,还是从新设置mmap分配阈值和mmap收缩阈值。
这个选项一般与M_MMAP_THRESHOLD选项一起使用,M_MMAP_THRESHOLD用于设置mmap分配阈值,对于长时间运行的程序,需要对这两个选项进行调优,尽量保证在ptmalloc中缓存的空闲chunk能够得到重用,尽量少用mmap分配临时用的内存。不停地使用系统调用mmap分配内存,然后很快又free掉该内存,这样是很浪费系统资源的,并且这样分配的内存的速度比从ptmalloc的空闲chunk中分配内存慢得多,由于需要页对齐导致空间利用率降低,并且操作系统调用mmap()分配内存是串行的,在发生缺页异常时加载新的物理页,需要对新的物理页做清0操作,大大影响效率。
M_TRIM_THRESHOLD的值必须设置为页大小对齐,设置为-1会关闭内存收缩设置。
注意:试图在程序开始运行时分配一块大内存,并马上释放掉,以期望来触发内存收缩,这是不可能的,因为该内存马上就返回给操作系统了。

3. M_MMAP_THRESHOLD
M_MMAP_THRESHOLD用于设置mmap分配阈值,默认值为128KB,ptmalloc默认开启动态调整mmap分配阈值和mmap收缩阈值。

当用户需要分配的内存大于mmap分配阈值,ptmalloc的malloc()函数其实相当于mmap()的简单封装,free函数相当于munmap()的简单封装。相当于直接通过系统调用分配内存,回收的内存就直接返回给操作系统了。因为这些大块内存不能被ptmalloc缓存管理,不能重用,所以ptmalloc也只有在万不得已的情况下才使用该方式分配内存。

但使用mmap分配有如下的好处:

l Mmap的空间可以独立从系统中分配和释放的系统,对于长时间运行的程序,申请长生命周期的大内存块就很适合有这种方式。

l Mmap的空间不会被ptmalloc锁在缓存的chunk中,不会导致ptmalloc内存暴增的问题。

l 对有些系统的虚拟地址空间存在洞,只能用mmap()进行分配内存,sbrk()不能运行。

使用mmap分配内存的缺点:

l 该内存不能被ptmalloc回收再利用。

l 会导致更多的内存浪费,因为mmap需要按页对齐。

l 它的分配效率跟操作系统提供的mmap()函数的效率密切相关,Linux系统强制把匿名mmap的内存物理页清0是很低效的。

所以用mmap来分配长生命周期的大内存块就是最好的选择,其他情况下都不太高效。

4. M_MMAP_MAX
M_MMAP_MAX用于设置进程中用mmap分配的内存块的最大限制,默认值为64K(cat /proc/sys/vm/max_map_count),因为有些系统用mmap分配的内存块太多会导致系统的性能下降。

如果将M_MMAP_MAX设置为0,ptmalloc将不会使用mmap分配大块内存。

Ptmalloc为优化锁的竞争开销,做了PER_THREAD的优化,也提供了两个选项,M_ARENA_TEST和M_ARENA_MAX,由于PER_THREAD的优化默认没有开启,这里暂不对这两个选项做介绍。
另外,ptmalloc没有提供关闭mmap分配阈值动态调整机制的选项,mmap分配阈值动态调整时默认开启的,如果要关闭mmap分配阈值动态调整机制,可以设置M_TRIM_THRESHOLD,M_MMAP_THRESHOLD,M_TOP_PAD和M_MMAP_MAX中的任意一个。但是强烈建议不要关闭该机制,该机制保证了ptmalloc尽量重用缓存中的空闲内存,不用每次对相对大一些的内存使用系统调用mmap去分配内存。

猜你喜欢

转载自blog.csdn.net/sun172270102/article/details/105717181