InnoDB的内存管理分为3层。1、在底层InnoDB建立一个通用内存池,负责为系统提供小块内存,另外InnoDB还建立缓冲池,能够为系统提供更大块的内存。二者都是向系统申请内存,只申请一次。其中,通用内存池只由中间层内存堆直接使用。2、在中间层建立一个内存堆对象,内存堆能够调用mem_area_alloc函数从通用内存池申请内存,称为动态申请,也能够一次调用buf_frame_alloc函数从缓冲池快速申请一页(默认16KB)内存,称为缓冲池申请。实现上,内存堆就像是一个栈,每次申请的内存块由双链表连接,它能够无限增加,可是它在释放时,每次只能释放栈顶的内存块,或者一次性释放整个内存堆的全部内存块。3、最上层为InnoDB的各个模块,当某个模块须要使用动态申请内存时,其向内存堆申请内存。使用模块分层设计的好处在于:(1)内存池一次从系统申请释放一大块内存,避免频繁调用malloc、free,提升性能。(2)一次申请一块大内存,减小内存外碎片。(3)容许内存堆从缓冲池快速申请一页内存。(4)分层设计实现功能的拆分,便于管理与实现。 node
InnoDB内存管理层次图 数据结构
下面说明InnoDB内存管理是如何工做的,使用内存块指明在内存堆中的由指针连接的一个个内存块,使用内存区指明内存池中由free_list列表管理的大小为2^i的多个连续内存区域。首先须要说明几个数据结构: 函数
1、TYPE类型双向链表 性能
#define UT_LIST_NODE_T(TYPE)\ spa
struct {\ 操作系统
TYPE * prev; /* pointer to the previous node,\ 设计
NULL if start of list */\ 指针
TYPE * next; /* pointer to next node, NULL if end of list */\ 对象
}\ 递归
2、用于管理TYPE类型双向链表的结构,指出链表包含的节点个数以及首尾节点
#define UT_LIST_BASE_NODE_T(TYPE)\
struct {\
ulint count; /* count of nodes in list */\
TYPE * start; /* pointer to list start, NULL if empty */\
TYPE * end; /* pointer to list end, NULL if empty */\
}\
3、通用内存池结构
struct mem_pool_struct{
byte* buf; /* 指向从操做系统申请获得的整个内存区域,全部的内存池操做都是在这一块大的内存区域上完成 */
ulint size; //内存池的大小
ulint reserved; //已经分配(使用)的内存大小
mutex_t mutex; //保护内存池的互斥量
UT_LIST_BASE_NODE_T(mem_area_t)
free_list[64]; /*一个用来管理内存池的列表,第i个负责管理2^i大小的内存区 */
};
4、内存区头
struct mem_area_struct{
ulint size_and_free; /* memory area size is obtained by
anding with ~MEM_AREA_FREE; area in
a free list if ANDing with
MEM_AREA_FREE results in nonzero */
UT_LIST_NODE_T(mem_area_t)
free_list; /* free list node */
};
InnoDB系统启动时内存管理模块的函数调用顺序以下。
InnoDB进程启动后内存管理模块初始化函数调用图
第一步,系统启动函数首先调用mem_pool_create函数从操做系统申请(malloc)内存并建立一个类型为mem_pool_struct的通用内存池实例mem_pool_struct *mem_comm_pool,其大小由参数srv_mem_pool_size定义,默认为8MB. 完成建立以后的内存池示意图:
初始状态的通用内存池
初始化以后,通用内存池向系统申请8MB内存,由free_list[23]元素管理该内存区。
第二步,调用mem_heap_create_func函数建立内存堆结构,并调用mem_heap_create_block函数建立其中的首个内存块。mem_heap_create_block调用mem_area_alloc函数从通用内存池申请内存,内部mem_area_alloc函数根据所申请的内存块大小计算该内存块须要被第i个free_list元素管理,若是free_list[i]上没有空闲内存,则调用mem_pool_fill_free_list函数,其根据该所需内存块所在free_list列表的位置,即i,递归调用本身,以从后续有空闲内存的free_list元素中切割它的内存区,而后将分半的内存区逐个向前传递,最终到达free_list[i],因而将该块内存返回给mem_heap_create_block。
例如,建立内存堆时须要申请一个大小为1MB的内存块(由于每次申请内存堆中的内存块时大小会成倍增加,所以首次申请大小一般较小,系统默认首次申请大小为MEM_BLOCK_START_SIZE,为64B,此处设为1MB,为了示意方便。),建立完成后内存堆的示意图以下。
初始状态的内存堆,只有一个可用内存块
上图所示,二分切割8MB的内存区,分为两个4MB的内存区,将第一个4MB区切割成两个2MB内存区,切割第一个2MB内存区获得两个空闲的1MB的内存区,将第一个空闲的1MB内存区分配和内存堆,每一个内存区的起始处包含一个mem_area_t结构,size_and_free记录当前内存区大小和是否已分配,同一free_list元素管理的空闲内存区由该结构中的两个指针prev、next连接起来。
内存池和内存堆建立完成以后,内存堆就可使用了,当InnoDB的某个模块须要申请动态内存时,仅需其调用mem_heap_alloc向内存堆申请内存。mem_heap_alloc调用图以下。
mem_heap_alloc函数调用图
mem_heap_alloc每次先找到内存堆中的最后一个内存块,检查该内存块的空闲空间是否足够,若是足够,就在该内存块上划出适量空间(将申请内存的size 按8字节向上对齐),若是当前快空闲空间不足,则调用mem_heap_add_block向内存池申请内存,由mem_heap_create_block函数建立新的内存块,再重新的内存块中划出空间返回给上层应用。
内存堆释放内存时,与内存的分配相反。先释放内存堆中的最后一个内存块的空间,且在内存块中释放每小块上层应用申请的内存时, mem_heap_free_heap_top只能自后向前释放,每次释放只需修改该内存块结构的free值,以标志当前块中空闲空间的偏移,若是当前内存块的空间所有释放,则调用mem_heap_block_free从堆中删除该内存块,该函数调用mem_area_free将该内存块的空间还给内存池。因为内存池采用伙伴系统来管理内存,所以mem_area_free首先调用mem_area_get_buddy函数获取当前内存区的伙伴buddy,而后判断buddy是否是空闲的,若是不是,则只需将该块内存区交给其对应的free_list元素管理;若是buddy是空闲的,则将该内存区与buddy合并,并递归调用mem_area_free函数,直到空闲内存区没法与其伙伴合并为大的空闲内存区,而后将获得的大块内存区交给对应的free_list元素管理。某个须要释放内存的函数调用顺序以下图所示。
释放内存的函数调用顺序图