c/c++容易出现内存泄露问题,这是因为它们需要直接操作内存,new/malloc分配内存,delete/free释放,需要一一对应,很多时候分配和释放内存之间有着复杂的过程,或者出错之前没有释放内存就出现内存泄露了,而这对于需要保证高可用性的数据库而言是不可接受的
MySQL通过MEM_ROOT可以保证分配的内存都被释放了,基本原理是:记录所有向操作系统'借'的所有内存块,通过alloc_root/multi_alloc_root函数向调用者返回相应大小内存,在释放时将其统一归还给操作系统,代价是:实际分配了更多的内存;额外的内存管理
代码位置:include/my_alloc.h,mysys/my_alloc.c
两个重要的数据结构:MEM_ROOT 和 USED_MEM
先来解释MEM_ROOT,这个结构体用于管理向操作系统'借'的所有内存块,free链表将有剩余空间的内存块链接起来,used链表记录所有空间使用得差不多(每次分配内存都会通过left_size或者first_block_usage判断是否将free中第一个block从free移到used中)的内存块,pre_alloc为预分配的内存空间,min_malloc会被初始化为32,first_block_usage这个参数比较有意思,每次从free链表中的第一个block分配内存失败(剩余空间不满足)后会增加1,而当在某次分配length大小内存时,如果发现free链表中的第一个内存块中没有足够的内存且剩余空间不够4096,而这时first_block_usage大于10的话,会将block从free链表移到used链表中,并且将first_block_usage重新置为0,error_handler是一个函数指针,看函数名就知道其作用了
- typedef struct st_mem_root
- {
- USED_MEM *free; /* blocks with free memory in it */
- USED_MEM *used; /* blocks almost without free memory */
- USED_MEM *pre_alloc; /* preallocated block */
- /* if block have less memory it will be put in 'used' list */
- size_t min_malloc;
- size_t block_size; /* initial block size */
- unsigned int block_num; /* allocated blocks counter */
- /*
- first free block in queue test counter (if it exceed
- MAX_BLOCK_USAGE_BEFORE_DROP block will be dropped in 'used' list)
- */
- unsigned int first_block_usage;
- void (*error_handler)(void);
- } MEM_ROOT;
继续来看USED_MEM这个结构体,它代表一个内存块,其中有内存块的总大小size,剩余大小left,还有一个指向下一个内存块的指针next,但是直到现在我们都还没有看到真正的内存在哪,MEM_ROOT如何向外分配出内存?
- typedef struct st_used_mem
- {
- struct st_used_mem *next; /* Next block in use */
- unsigned int left; /* memory left in block */
- unsigned int size; /* size of block */
- } USED_MEM;
其实,USED_MEM这个结构体中能够看到的成员实际上一个内存块的元数据,真正的的内存是紧接着该结构体之后,内存大小为size-ALIGN_SIZE(sizeof(USED_MEM)),通过left记录剩余空闲内存大小,可以看出内存块之间通过next指针形成了一个链表
到这里,再看MEM_ROOT中的一下函数就很容易了:
init_alloc_root
初始化MEM_ROOT,如果pre_alloc_size不为0,就预分配一个大小为pre_alloc_size+ ALIGN_SIZE(sizeof(USED_MEM)的内存块,并将pre_alloc和free指针指向它,看出来了,真正的内存从结构体地址之后的ALIGN_SIZE(sizeof(USED_MEM)开始
alloc_root
向外分配内存的函数,从free链表中找到一个大小满足的内存块,对于第一个block,会执行first_block_usage++; 如果其中没有足够的内存且剩余空间不够4096,而这时first_block_usage大于10的话,会将该block从free链表移到used链表中,继续搜索链表中的block,如果能找到一个大小满足的块,就从其中分配,如果没能找到,就向操作系统'借'一块内存,大小为block_size * (block_num >> 2)和length+ALIGN_SIZE(sizeof(USED_MEM))中的最大值,将其放到free链表最后,分配完内存后,block中left会减小分配出去的大小,如果left
multi_alloc_root
弄明白了alloc_root,这个就很容易了,这里不做过多解释
free_root
标记释放:MY_MARK_BLOCKS_FREE,将free和used链表中所有block中的left重置(size-ALIGN_SIZE(sizeof(USED_MEM)),used链到free链表末尾,以及重置MEM_ROOT中的used和first_block_usage
真正释放:遍历free和used链表,向操作系统归还所有'借'来的内存块
阅读(3950) | 评论(0) | 转发(0) |