初入堆的知识点

初入堆

堆申请

堆简介

堆是用malloc函数申请使用的。是虚拟地址空间的一块连续的线性区域,能够提供动态分配的内存,允许程序申请大小未知的内存,在用户与操作系统之间,作为动态内存管理的中间人,能够响应用户的申请内存请求,向操作系统申请内存,然后将返回给用户程序,管理用户所释放的内存,适时归还给操作系统。

 

chunk

定义

chunk是用户申请的内存单位,也是对管理器内存的基本单位malloc()返回的指针指向一个chunk的数据区域。运行过程中被malloc分配的内存为一个chunk,这块内存在ptmalloc中用malloc_chunk结构体表示,当程序申请的chunk被free时,会被加入相应的空闲管理列表中。

  • 被用户使用中的叫 allocated chunk

  • 被用户释放,处于空闲的叫做 free chunk

  • top chunk

  • last remainder chunk

chunk的分类  
按状态 按大小 按特定功能
malloced fast top chunk
free small last remainder chunk
  targe  
  tcache  

mallo_chunk结构


struct malloc_chunk {

 INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free). */
 INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

 struct malloc_chunk* fd;         //双向链表指针
 struct malloc_chunk* bk;

 /* Only used for large blocks: pointer to next larger size. */
 struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
 struct malloc_chunk* bk_nextsize;
};
  1. prev_size:如果前一个chunk是空闲的,该域表示前一个chunk的大小,如果前一个chunk不空闲,该域毫无意义

  2. size:当前chunk大小,并且记录了当前chunk和前一个chunk的一些属性(通过二进制后三位记录)。

  3. fd:记录了下一个被free的chunk(used only if free),即下一个被free掉的chunk

  4. bk:记录了上一个被free的chunk(used only if free),即上一个被free掉的chunk

  5. fd_nextsize 和 bk_nextsize,largebin使用,记录上/下一个被free chunk的size

一个已经分配的 chunk 的样子如下。我们称前两个字段称为 chunk header,后面的部分称为 user data。每次 malloc 申请得到的内存指针,其实指向 user data 的起始处。

使用状态的chunk:

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             Size of previous chunk, if unallocated (P clear) |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             Size of chunk, in bytes                     |A|M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             User data starts here...                         .
      .                                                               .
      .             (malloc_usable_size() bytes)                     .
next   .                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             (size of chunk, but used for application data)   |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             Size of next chunk, in bytes               |A|0|1|
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

在这里插入图片描述

第二个部分的高位存储当前 chunk 的大小,低 3 位为size域中的标志位 分别表示:

  • N / A: NON_MAIN_ARENA 当前 chunk 是否属于主线程 0 表示主线程的堆块结构 ; 1 表示子线程的堆块结构

  • M: IS_MMAPED 当前 chunk 是否由mmap分配的 0 表示由堆块中的top chunk分裂产生; 1 表示由mmap分配

  • P: PREV_INUSE 前一个的 chunk 是否处于空闲状态 0 表示处于空闲状态; 1 表示处于使用状态(主要用来判断free时是否能与上一块进行合并)

    当一个 chunk 处于使用状态时,它的下一个 chunk 的 prev_size 域无效,所以下一个 chunk 的该部分也可以被当前chunk使用。这就是chunk中的空间复用。

被释放的 chunk 被记录在链表中(可能是循环双向链表,也可能是单向链表)。具体结构如下

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             Size of previous chunk, if unallocated (P clear) |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' |             Size of chunk, in bytes                     |A|0|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             Forward pointer to next chunk in list             |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             Back pointer to previous chunk in list           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             Unused space (may be 0 bytes long)               .
      .                                                               .
next   .                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:' |             Size of chunk, in bytes                           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |             Size of next chunk, in bytes               |A|0|0|
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

在这里插入图片描述

可以发现,如果一个 chunk 处于 free 状态,那么会有两个位置记录其相应的大小

  1. 本身的 size 字段会记录,

  2. 它后面的 chunk 会记录。

allocated chunk/已分配内存块

结构如下

img


第一个部分(32 位上 4B,64 位上 8B)叫做prev_size,只有在前一个 chunk 空闲时才表示前一个块的大小,否则这里就是无效的,可以被前一个块征用(存储用户数据)。

这里的前一个chunk,指内存中相邻的前一个,而不是freelist链表中的前一个。PREV_INUSE代表的“前一个chunk”同理。

第二个部分的高位存储当前 chunk 的大小,低 3 位为size域中的标志位 分别表示:

  • N / A: NON_MAIN_ARENA 当前 chunk 是否属于主线程 0 表示主线程的堆块结构 ; 1 表示子线程的堆块结构

  • M: IS_MMAPED 当前 chunk 是否由mmap分配的 0 表示由堆块中的top chunk分裂产生; 1 表示由mmap分配

  • P: PREV_INUSE 前一个的 chunk 是否处于空闲状态 0 表示处于空闲状态; 1 表示处于使用状态(主要用来判断free时是否能与上一块进行合并)

Free chunk/未分配内存块

结构如下

img

首先,prev_size必定存储上一个块的用户数据,因为 Free chunk 的上一个块必定是 Allocated chunk,否则会发生合并。

接着,多出来的fd指向同一个 bin 中的前一个 Free chunk,bk指向同一个 bin 中的后一个 Free chunk。

一般情况下,物理相邻的两个空闲 chunk 会被合并为一个 chunk 。堆管理器会通过 prev_size 字段以及 size 字段合并两个物理相邻的空闲 chunk 块。

Top chunk/顶部内存块

img

一个arena顶部的 chunk 叫做 Top chunk,它不属于任何 bin。当所有 bin 中都没有空闲的可用 chunk 时,我们切割 Top chunk 来满足用户的内存申请。假设 Top chunk 当前大小为 N 字节,用户申请了 K 字节的内存,那么 Top chunk 将被切割为:

  • 一个 K 字节的 chunk,分配给用户

  • 一个 N-K 字节的 chunk,称为 Last Remainder chunk

后者成为新的 Top chunk。如果连 Top chunk 都不够用了,那么:

  • main_arena中,用brk()扩张 Top chunk

  • non_main_arena中,用mmap()分配新的堆

注:Top chunk 的 PREV_INUSE 位总是 1

last remainder chunk

当需要分配一个比较小的 K 字节的 chunk 但是 small bins 中找不到满足要求的,且 Last Remainder chunk 的大小 N 能满足要求,那么 Last Remainder chunk 将被切割为:

  • 一个 K 字节的 chunk,分配给用户

  • 一个 N-K 字节的 chunk,成为新的 Last Remainder chunk

它的存在使得连续的小空间内存申请,分配到的内存都是相邻的,从而达到了更好的局部性。

堆的释放

堆的释放一般都用free函数实现,并且当alloced chunk被释放后,会放入bin或者合并到top chunk中去。bin的主要作用是加快分配速度,其通过链表方式(chunk结构体中的fd和bk指针)进行管理。

堆释放后的管理

堆释放后,会被添加到相应的bins中进行管理,这里涉及的结构体是malloc_state。 分箱式管理:对于空闲chunk,ptmalloc采用分箱式内存管理方式,根据空闲chunk大小和处于的状态将其放在四个不同的bin中,这四个空闲的chunk容器包括:fast bins、unsorted bin、small bins和large bins

堆空闲块管理结构bin

当alloced chunk被释放后,会放入bin或者合并到top chunk中去。bin的主要作用是加快分配速度,其通过链表方式(chunk结构体中的fd和bk指针)进行管理。

可以分为:

  • 10 个 fast bins,存储在fastbinsY

  • 1 个 unsorted bin,存储在bin[1]

  • 62 个 small bins,存储在bin[2]bin[63]

  • 63 个 large bins,存储在bin[64]bin[126]

Fast bins

Fast bins 是小内存块的高速缓存,当一些大小小于64字节的chunk被回收时,首先会被放入fast bins 中,在分配小内存是,首先会查看fast bins中是否有合适的内存块,如果存在,则直接返回fast bins中的内存块,从而加快分配速度。

  • 除了fast bins的结构是单项链表,其他都是双向链表。因为fast bins只有一个fd指针

  • fast bins的工作方式是后进先出。

  • fast bins的P永远是1,这样可以避免被合并,从而更快的释放和分配

  • fastbin管理16、24、32、40、48、56、64bytes的free chunks(32位下默认)

在64位系统中,保存的堆块大小在0x200x80之间;在32位系统中,其大小区间为0x100x40(x86)

unstorted bin

unsorted bin 可以视为空闲 chunk 回归其所属 bin 之前的缓冲区。主要用于存放刚释放的堆块以及大堆块分配后剩余的堆块,大小没有限制。

内存中堆块的对齐规则

  • 32位系统中,按0x8字节对齐 ,chunk最小为0x10字节。

  • 64位系统中,按0x10字节对齐,chunk最小为0x20字节。8

  •  
posted @   T_FIRE  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示