malloc,ptmalloc内存分配及回收原理

malloc函数要求:

1.malloc函数分配的内存大小至少为size参数所指定的字节数
2.malloc的返回值是一个void类型的指针,必须强转为我们需要的类型的指针
3.多次调用malloc所分配的地址不能有重叠部分,除非某次malloc所分配的地址被free释放了
4.malloc应尽快完成内存分配并且返回
5.实现malloc的同时实现calloc和realloc和free

malloc分配的内存空间是连续的吗?

使用malloc分配的内存空间在虚拟地址空间上是连续的,但是转换到物理内存空间上有可能是不连续的,因为有可能相邻的两个字节是在不同的物理分页上;

进程分配内存的两种方式:brk和mmap

1.brk是将数据段(.data)的最高地址指针_edata往高地址推;
2.mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。
这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

malloc小于128k的内存,使用brk分配内存,将_edata往高地址推(只分配虚拟空间,不对应物理内存(因此没有初始化),第一次读/写数据时,引起内核缺页中断,内核才分配对应的物理内存,然后虚拟地址空间建立映射关系)
如果用malloc分配了A这块内容,然后从来不访问它,那么,A对应的物理页是不会被分配的。

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

malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不是去推_edata指针了,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存。
这样子做主要是因为::
brk分配的内存需要等到高地址内存释放以后才能释放(例如,在B释放之前,A是不可能释放的,这就是内存碎片产生的原因,什么时候紧缩看下面),而mmap分配的内存可以单独释放。

ptmalloc:

ptmalloc有可能在两个地方为用户分配内存空间。
第一次分配内存时,只存在一个主分配区,也有可能在父进程那里继承了多个非主分配区。
这里写图片描述

通过chunk的数据结构来组织每个内存单元。当我们使用malloc分配得到一块内存的时候,这块内存就会通过chunk的形式被记录到glibc上并且管理起来。你可以把它想象成自己写内存池的时候的一个内存数据结构。
chunk的结构可以分为使用中的chunk和空闲的chunk。

正在使用中的chunk:
这里写图片描述

  1. chunk指针指向chunk开始的地址;mem指针指向用户内存块开始的地址。
  2. p=0时,表示前一个chunk为空闲,prev_size才有效
  3. p=1时,表示前一个chunk正在使用,prev_size无效 p主要用于内存块的合并操作
  4. ptmalloc 分配的第一个块总是将p设为1, 以防止程序引用到不存在的区域
  5. M=1 为mmap映射区域分配;M=0为heap区域分配
  6. A=1 为非主分区分配;A=0 为主分区分配

空闲的chunk:
这里写图片描述
1. 空闲的chunk会被放置到空闲的链表bins上。当用户申请内存malloc的时候,会先去查找空闲链表bins上是否有合适的内存。
2. fd和bk分别指向前一个和后一个空闲链表上的chunk
3. fd_nextsize和bk_nextsize分别指向前一个空闲chunk和后一个空闲chunk的大小,主要用于在空闲链表上快速查找合适大小的chunk。
4. fd、bk、fd_nextsize、bk_nextsize的值都会存在原本的用户区域,这样就不需要专门为每个chunk准备单独的内存存储指针了。

chunk的空间复用:

目的:为了使得chunk所占用的空间最小
空闲的chunk和使用中的chunk使用的是同一块空间取其中最大者作为实际的分配空间

last remainder:需要分配一个small chunk,但在small bins里找不到合适的chunk。如果last….的大小大于所需的small chunk 大小,last…被分裂成两个chunk,其中一个chunk返回给用户,另一个chunk变成新的last…

空闲链表bins:

当用户使用free函数释放掉的内存,ptmalloc将内存由空闲链表bins管理起来,当下次进程需要malloc一块内存时,ptmalloc就会从空闲的bins上寻找一块合适大小的内存块分配给用户使用。
这里写图片描述

fast bins:

是bins的高速缓冲区,当用户释放一块不大于max_fast的chunk的时候,被默认放在fast bins上,当用户下次需要申请内存的时候首先会到fast bins寻找,再到bins上找。

unsorted bin:

bins的缓冲区。当用户释放的内存大于max_fast或者fast bins合并后的chunk都会进入unsorted bin上,当用户malloc的时候,先会到unsorted bin上查找是否有合适的bin,如果没有合适的bin,ptmalloc会将unsorted bin上的chunk放入bins上,然后到bins上查找合适的空闲chunk。

mall bins和large bins:

是真正用来放置chunk双向链表的。每个bin之间相差8个字节,并且通过上面的这个列表,可以快速定位到合适大小的空闲chunk。前64个为small bins,定长;后64个为large bins,非定长。

Top chunk:

并不是所有的chunk都会被放到bins上。top chunk相当于分配区的顶部空闲内存,当bins上都不能满足内存分配要求的时候,就会来top chunk上分配。

mmaped chunk:

当分配的内存非常大(大于分配阀值,默认128K)的时候,需要被mmap映射,则会放到mmaped chunk上,当释放mmaped chunk上的内存的时候会直接交还给操作系统。

内存分配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会变为空。合并后的chunktopchunk相邻,则会合并到topchunk中。转到步骤8
  8. 判断top chunk的大小是否大于mmap收缩阈值(默认为128KB),如果是的话,对于主分配区,则会试图归还top chunk中的一部分给操作系统。free结束。

注意事项:

  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被系统杀掉。
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值