ptmalloc源码剖析

ptmalloc内存管理器

ptmalloc是glibc默认的内存管理器。我们常用的malloc和free就是由ptmalloc内存管理器提供的基础内存分配函数。ptmalloc有点像我们自己写的内存池,当我们通过malloc或者free函数来申请和释放内存的时候,ptmalloc会将这些内存管理起来,并且通过一些策略来判断是否需要回收给操作系统。这样做的最大好处就是:让用户申请内存和释放内存的时候更加高效。(假如每次malloc都需要进行系统调用,开销就会很大)

为了内存分配函数malloc的高效性,ptmalloc会预先向操作系统申请一块内存供用户使用,并且ptmalloc会将已经使用的和空闲的内存管理起来;当用户需要销毁内存free的时候,ptmalloc又会将回收的内存管理起来,根据实际情况是否回收给操作系统

主分配区main_area非主分配区no_main_area

ptmalloc的内存分配器中,为了解决多线程锁争夺问题,分为主分配区和非主分配区。

1. 每个进程有一个主分配区,也可以允许有多个非主分配区。

2. 主分配区可以使用brk和mmap来分配,而非主分配区只能使用mmap来映射内存块

3. 非主分配区的数量一旦增加,则不会减少。

4. 主分配区和非主分配区形成一个环形链表进行管理。

使用中的chunk:

ptmalloc源码剖析

空闲的chunk:

ptmalloc源码剖析

空闲链表bins

当用户使用free函数释放掉的内存,ptmalloc并不会马上交还给操作系统(这边很多时候我们明明执行了free函数,但是进程内存并没有回收就是这个原因),而是被ptmalloc本身的空闲链表bins管理起来了,这样当下次进程需要malloc一块内存的时候,ptmalloc就会从空闲的bins上寻找一块合适大小的内存块分配给用户使用。这样的好处可以避免频繁的系统调用,降低内存分配的开销。

ptmalloc源码剖析

ptmalloc一共维护了128bin。每个bins都维护了大小相近的双向链表的chunk

ptmalloc源码剖析

ptmalloc源码剖析

ptmalloc源码剖析

ptmalloc源码剖析

内存分配malloc流程

1. 获取分配区的锁,防止多线程冲突。

2. 计算出需要分配的内存的chunk实际大小。

3. 判断chunk的大小,如果小于max_fast(64b),则取fast bins上去查询是否有适合的chunk,如果有则分配结束。

4. chunk大小是否小于512B,如果是,则从small bins上去查找chunk,如果有合适的,则分配结束。

5. 继续从 unsorted bins上查找。如果unsorted bins上只有一个chunk并且大于待分配的chunk,则进行切割,并且剩余的chunk继续扔回unsorted bins;如果unsorted bins上有大小和待分配chunk相等的,则返回,并从unsorted bins删除;如果unsorted bins中的某一chunk大小 属于small bins的范围,则放入small bins的头部;如果unsorted bins中的某一chunk大小 属于large bins的范围,则找到合适的位置放入。

6. 从large bins中查找,找到链表头后,反向遍历此链表,直到找到第一个大小 大于待分配的chunk,然后进行切割,如果有余下的,则放入unsorted bin中去,分配则结束。

7. 如果搜索fast bins和bins都没有找到合适的chunk,那么就需要操作top chunk来进行分配了(top chunk相当于分配区的剩余内存空间)。判断top chunk大小是否满足所需chunk的大小,如果是,则从top chunk中分出一块来。

8. 如果top chunk也不能满足需求,则需要扩大top chunk。主分区上,如果分配的内存小于分配阈值(默认128k),则直接使用brk()分配一块内存;如果分配的内存大于分配阈值,则需要mmap来分配;非

主分区上,则直接使用mmap来分配一块内存。通过mmap分配的内存,就会放入mmap chunk上,mmap chunk上的内存会直接回收给操作系统。

内存释放free流程

1. 获取分配区的锁,保证线程安全。

2. 如果free的是空指针,则返回,什么都不做。

3. 判断当前chunk是否是mmap映射区域映射的内存,如果是则直接munmap()释放这块内存。前面已使用chunk的数据结构中,我们可以看到有M来标识是否是mmap映射的内存。

4. 判断chunk是否与top chunk相邻,如果相邻,则直接和top chunk合并(和top chunk相邻相当于和分配区中的空闲内存块相邻)。转到步骤8

5. 如果chunk的大小大于max_fast(64b),则放入unsorted bin,并且检查是否有合并,有合并情况并且和top chunk相邻,则转到步骤8;没有合并情况则free。

6. 如果chunk的大小小于 max_fast(64b),则直接放入fast bin,fast bin并没有改变chunk的状态。没有合并情况,则free;有合并情况,转到步骤7

7. 在fast bin,如果当前chunk的下一个chunk也是空闲的,则将这两个chunk合并,放入unsorted bin上面。合并后的大小如果大于64KB,会触发进行fast bins的合并操作,fast bins中的chunk将被遍历,并与相邻的空闲chunk进行合并,合并后的chunk会被放到

unsorted bin中,fast bin会变为空。合并后的chunk和topchunk相邻,则会合并到topchunk中。转到步骤8

8. 判断top chunk的大小是否大于mmap收缩阈值(默认为128KB),如果是的话,对于主分配区,则会试图归还top chunk中的一部分给操作系统。free结束。

使用注意事项

为了避免Glibc内存暴增,需要注意:

1. 后分配的内存先释放,因为ptmalloc收缩内存是从top chunk开始,如果与top chunk相邻的chunk不能释放,top chunk以下的chunk都无法释放

2. Ptmalloc不适合用于管理长生命周期的内存,特别是持续不定期分配和释放长生命周期的内存,这将导致ptmalloc内存暴增。

3. 多线程分阶段执行的程序不适合用ptmalloc,这种程序的内存更适合用内存池管理

4. 尽量减少程序的线程数量和避免频繁分配/释放内存。频繁分配,会导致锁的竞争,最终导致非主分配区增加,内存碎片增高,并且性能降低。

5. 防止内存泄露,ptmalloc对内存泄露是相当敏感的,根据它的内存收缩机制,如果与top chunk相邻的那个chunk没有回收,将导致top chunk一下很多的空闲内存都无法返回给操作系统。

6. 防止程序分配过多内存,或是由于Glibc内存暴增,导致系统内存耗尽,程序因OOM被系统杀掉。预估程序可以使用的最大物理内存大小,配置系统的/proc/sys/vm/overcommit_memory,/proc/sys/vm/overcommit_ratio,以及使用ulimt –v限制程序能使用虚拟内存空间大小,防止程序因OOM被杀掉。