初入堆的知识点
堆申请
堆简介
堆是用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;
};
-
prev_size:如果前一个chunk是空闲的,该域表示前一个chunk的大小,如果前一个chunk不空闲,该域毫无意义
-
size:当前chunk大小,并且记录了当前chunk和前一个chunk的一些属性(通过二进制后三位记录)。
-
fd:记录了下一个被free的chunk(used only if free),即下一个被free掉的chunk
-
bk:记录了上一个被free的chunk(used only if free),即上一个被free掉的chunk
-
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 状态,那么会有两个位置记录其相应的大小
-
本身的 size 字段会记录,
-
它后面的 chunk 会记录。
allocated chunk/已分配内存块
结构如下
第一个部分(32 位上 4B,64 位上 8B)叫做prev_size
,只有在前一个 chunk 空闲时才表示前一个块的大小,否则这里就是无效的,可以被前一个块征用(存储用户数据)。
这里的前一个chunk,指内存中相邻的前一个,而不是freelist链表中的前一个。
PREV_INUSE
代表的“前一个chunk”同理。
第二个部分的高位存储当前 chunk 的大小,低 3 位为size域中的标志位 分别表示:
-
N / A:
NON_MAIN_ARENA
当前 chunk 是否属于主线程 0 表示主线程的堆块结构 ; 1 表示子线程的堆块结构 -
-
P:
PREV_INUSE
前一个的 chunk 是否处于空闲状态 0 表示处于空闲状态; 1 表示处于使用状态(主要用来判断free时是否能与上一块进行合并)
Free chunk/未分配内存块
结构如下
首先,prev_size
必定存储上一个块的用户数据,因为 Free chunk 的上一个块必定是 Allocated chunk,否则会发生合并。
接着,多出来的fd
指向同一个 bin 中的前一个 Free chunk,bk
指向同一个 bin 中的后一个 Free chunk。
一般情况下,物理相邻的两个空闲 chunk 会被合并为一个 chunk 。堆管理器会通过 prev_size 字段以及 size 字段合并两个物理相邻的空闲 chunk 块。
Top chunk/顶部内存块
一个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
-
本文来自博客园,作者:T_FIRE,转载请注明原文链接:https://www.cnblogs.com/TFIRE/p/18578657
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?