malloc原理分析:ptmalloc2


glibc中malloc采用的是ptmalloc2内存池管理方式,使用边界标记法将内存划分成很多块,从而对内存的分配与回收进行管理。

多线程:主分配区和非主分配区

为了支持多线程,ptmalloc使用两类分配区:主分配区main_area和非主分配区no_main_area。

  • 主分配区和非主分配区形成一个环形链表进行管理。
  • 每一个分配区利用互斥锁使线程对于该分配区的访问互斥。
  • 每个进程只有一个主分配区,可以有多个非主分配区。

当一个线程需要使用malloc分配内存的时候,会先查看该线程的私有变量中是否已经存在一个分配区。若存在,会尝试对其进行加锁操作。若是加锁成功,就在使用该分配区分配内存,若是失败,就会遍历循环链表中获取一个未加锁的分配区。若是整个链表中都没有未加锁的分配区,则malloc会开辟一个新的分配区,将其加入全局的循环链表并加锁,然后使用该分配区进行内存分配。当释放这块内存时,同样会先获取待释放内存块所在的分配区的锁。若是有其他线程正在使用该分配区,则必须等待其他线程释放该分配区互斥锁之后才能进行释放内存的操作。

结构

chunk

ptmalloc使用chunk结构体描述内存块,内部包含大小、前后chunk指针、前一个 chunk 是否在使用中、前一个 chunk 的大小等成员。
在这里插入图片描述

  • p主要用于内存块的合并操作;
    • p=0时,表示前一个chunk为空闲,prev_size才有效;
    • p=1时,表示前一个chunk正在使用,prev_size无效
    • ptmalloc 分配的第一个块总是将p设为1, 以防止程序引用到不存在的区域
  • M=1 为mmap映射区域分配,M=0为heap区域分配
  • A=0 为主分配区分配,A=1 为非主分配区分配。

空闲chunk在内存中的结构:

在这里插入图片描述

  • fp和bp分别指向前一个和后一个空闲链表上的chunk
  • fp_nextsize和bp_nextsize分别指向前一个空闲chunk和后一个空闲chunk的大小,主要用于在空闲链表上快速查找合适大小的chunk。
  • fp、bp、fp_nextsize、bp_nextsize的值都会存在原本的用户区域,这样就不需要专门为每个chunk准备单独的内存存储指针了。

bin

相似大小的chunk在free后被malloc用双向链表链接起来,这样一个链表被称为一个bin,一共有128个bin。基于chunk的大小,bin一共有四种:

  1. Fast bin ( < 64B )
  2. Unsorted bin
  3. Small bin
  4. Large bin

这些bins被保存在数组fastbinsY(fast bin)和bins(其他bin)中。

在这里插入图片描述

当用户调用malloc的时候,能很快找到用户需要分配的内存大小是否在维护的bin上,如果在某一个bin上,就可以通过双向链表去查找合适的chunk内存块给用户使用。

fast bins

fast bins 记录着大小以8字节递增的 Fast bin链表。fast bin用来存储较小( size < 默认max_fast 64B)的内存,其中chunk一般不会合并,因此速度很快。

unsorted bin

unsorted bin 的队列使用 bins 数组的第一个,相当于small bins和large bins的一个缓冲区,存放最近释放的大小 > max_fast 的chunk、合并后的chunk以及切割剩余的chunk等,无尺寸上限,可以快速找到最近free的chunk。如果找不到合适大小的chunk,ptmalloc会清空unsorted bin,将其中的chunks 按大小分类放进合适的其他bin中。

small bins

small bin保存大小小于512字节的chunk,从2开始编号,一共有63个,相邻的small bin之间相差8字节。同一个small bin中的chunk具有相同大小。

在free一个chunk的时候,检查其前或其后的chunk是否空闲,若是则合并,也即把它们从所属的链表中摘除并合并成一个新的chunk,新chunk会添加在unsorted bin链表的前端。

large bins

large bin保存大小大于等于512字节的chunk,位于small bin后面。每一个bin包含了一个给定范围内的chunk,其中的chunk按大小递减排序。

分配chunk时,寻找大小最合适的chunk,并且进行切割,剩余部分放进unsorted bin。free类似small bin,满足条件时会进行合并。

除以上类型的chunk外还有 mmaped chunk 和 top chunk :

mmaped chunk

当分配的内存非常大(大于分配阀值,默认128K)的时候,直接使用mmaped进行匿名内存映射申请内存,当释放mmaped chunk上的内存的时候会直接交还给操作系统。

top chunk

top chunk相当于分配区的顶部空闲内存,当bins上都不能满足内存分配要求且小于mmap分配阈值的时候,就会来top chunk上分配。

  • 当top chunk大小比用户所请求大小还大的时候,top chunk会被切割为两个部分:User chunk(用户请求大小)和Remainder chunk(剩余大小)。其中Remainder chunk成为新的top chunk。
  • 当top chunk大小小于用户所请求的大小时,top chunk就通过sbrk(主分配区)或mmap(非主分配区)系统调用来扩容。

Last remainder

Last remainder是另外一种特殊的chunk,就像top chunk和mmaped chunk一样,不会在任何bins中找到这种chunk。
当需要分配一个small chunk,但在small bins中找不到合适的chunk,如果last remainder chunk的大小大于所需的small chunk大小,last remainder chunk被分裂成两个chunk,其中一个chunk返回给用户,另一个chunk变成新的last remainder chuk。

流程

分配初始化

ptmalloc 在开始时heap大小为0,若请求的空间小于 mmap 分配阈值(mmap threshold,默认值为 128KB)时,

  • 主分配区会调用 sbrk()增加一块大小为 (128 KB + chunk_size) align 4KB 的空间作为 heap。
  • 非主分配区会调用 mmap 映射一块大小为 HEAP_MAX_SIZE(32 位系统上默认为 1MB,64 位系统上默认为 64MB)的空间作为 sub-heap。

这就是 ptmalloc 所维护的分配空间。当用户请求内存分配时,首先会在这个区域内找一块合适的 chunk 给用户。当用户释放了 heap 中的 chunk 时,ptmalloc 又会使用 fastbins 和 bins 来组织空闲 chunk。以备用户的下一次分配。

malloc流程

  1. 获取分配区(arena)并加锁;
  2. 根据内存块大小选择bin,如果小于64B,去fast bins上寻找,找到结束;否则,转下一步;
    1. 大小小于512B去small bins找,
      • 找到,则分配结束;
      • 找不到,进行下一步;
    2. 大小大于512B或者small bins找不到,则遍历fast bins中的chunk,将相邻的chunk进行合并,并连接到unsorted bin中。然后遍历unsorted bin中的chunk。
      • 找到比所需大小大的chunk,则进行切割;
      • 没找到,清空unsorted chunk,将其中chunk按大小放入small或large bins,进行下一步;
  3. 到了这一步说明分配的是一块很大的内存,或者是在unsorted bin和small bins中都没有找到合适的chunk,从large bins中找到一个合适的chunk进行切割,并将剩下的部分连接回unsorted bins中,如果操作成功就结束分配,否则,转下一步。
  4. bins没有找到合适的chunk,那么需要操作top chunk来进行分配了。判断top chunk大小是否满足所需要的chunk的大小,如果是,则从top chunk中分出一块来;否则,转下一步;
  5. 若所需chunk大小大于mmap分配阈值,则使用mmap进行分配,否则增大top chunk大小
    • 主分配区,调用 sbrk(),增加 top chunk 大小。
    • 非主分配区,调用 mmap 来分配一个新的 sub-heap,增加 top chunk 大小。并且将top chunk迁移到新的sub heap上。新的sub heap将和旧的连接起来,然后在从里面分配内存。

流程可以概括为:

  1. 分配区加锁;

  2. 如果小于512B去fast bins/small bins中寻找,找不到或者大于512B则去unsorted bin找,找到则切割,没找到则将其清空,并且去large bins找,找到则切割chunk(余下放入unsorted bin);

  3. 都没有找到,则操作top chunk,top chunk足够大则切割;若不够大,则在chunk大小大于分配阈值时选择mmap,或者根据分配区种类扩大top chunk:

    • 主分配区使用sbrk(),

    • 非主分配区使用mmap分配新的sub-heap。

内存回收流程

  1. 获取分配区的锁,保证线程安全。
  2. 如果free的是空指针,则返回,什么都不做。
  3. 判断当前chunk是否是mmap映射区域映射的内存,如果是,则直接munmap()释放这块内存。在chunk的数据结构中,有M来标识是否是mmap映射的内存。
  4. 判断chunk是否与top chunk相邻,如果相邻,则直接和top chunk合并,转到步骤8,否则转下一步;
  5. 如果chunk的大小大于max_fast(64B),则放入unsorted bin,并且检查是否有合并,有合并情况并且和top chunk相邻,则转到步骤8;没有合并情况free()返回。
  6. 如果chunk的大小小于 max_fast(64B),则直接放入fast bin。没有合并情况,free()返回;有合并情况,转下一步;
  7. 在fast bin,如果当前chunk的下一个chunk也是空闲的,则将这两个chunk合并,放入unsorted bin。合并后的大小如果大于64B,会触发进行fast bins的合并操作,fast bins中的chunk将被遍历,并与相邻的空闲chunk进行合并,合并后的chunk会被放到unsorted bin中,fast bin会变为空。合并后的chunk和topchunk相邻,则会合并到topchunk中。转到步骤8
  8. 判断top chunk的大小是否大于mmap收缩阈值(默认为128KB),如果是的话,
    • 对于主分配区,则会试图归还top chunk中的一部分给操作系统,free()返回;
    • 如果为非主分配区,会进行sub-heap收缩,将top chunk的一部分返回给操作系统,如果top chunk为整个sub-heap,会把整个sub-heap还回给操作系统,free()返回。

ptmalloc的管理的内存,只有在top chunk足够大时,返回给OS。

如何避免内存暴增?

1、后分配的内存先释放。因为ptmalloc收缩内存是从top chunk开始的,如果 top chunk相邻的chunk不能释放,top 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被杀掉。

Linux如何使用tcmalloc代替ptmalloc?

  1. 下载release版本gperftools.
  2. 安装gperftools:
    ./configure
    make -j4
    sudo make install
    
  3. 然后编译程序时加上-ltcmalloc即可。
    CMAKE中target_link_libraries()加上tcmalloc。

参考:
https://www.iteye.com/blog/mqzhuang-1005909
http://blog.sina.com.cn/s/blog_e739607d0102wqjx.html
https://blog.csdn.net/z_ryan/article/details/79950737

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值