Oceanbase 中有一个全局内存池,系统内很多模块都是用的这个全局内存池,它的接口在common/ob_malloc.h 中,当然,最主要的就是
ob_malloc()和ob_free() 这两个接口了.本文主要分析这两个接口.
这个全局内存池本质上是一个定长内存池,有两个链表,一个是被使用的块的链表,另外一个是空闲块的链表,当ob_malloc()请求的内存小于一个block的大小时,就直接从空闲链表中拿出一个空闲的块分配给它,并且插入到被使用的块的链表中 ; 当ob_free()的时候,把这个块归还给空闲链表。当ob_malloc()请求的内存大于一个block的大小时,就直接从系统new一块内存给它,并且也将这个块插入到被使用的块的链表中,在这种情况下,这个block的大小是比标准的block更大,以后调用ob_free()的时候,就直接通过delete归还给系统,就不会放入空闲链表中。
值得注意的是,当ob_malloc()请求的内存比标准的block的大小小很多的时候,系统也会将一整个block分配给它,这样造成了一定的内存的浪费,作者的本意是一个定长block中有若干个item,分配内存的时候是以item为单位的。目前为了实现简单,一个block中只有一个item.
系统中,定长内存池由类 ObFixedMemPool 表示,它继承于 ObBaseMemPool,Oceanbase中内存池有一个比较好的特性就是可以统计各个模块的内存使用量,以便
发生内存泄漏的时候可以快速定位。关于模块内存使用量统计相关的部分在ObBaseMemPool中,主要有如下一些成员:
int64_t *mem_size_each_mod_;// 这个名字取的比较诡异,实际上这个成员变量指向一个数组,每个元素存放一个模块的内存使用量,系统一共支持2048个模块
const ObMemPoolModSet *mod_set_; //这个指针可以访问系统中所有模块的信息,主要就是模块id和模块名称,所有模块的信息实际上是存放在一个2048个元素的数组中,实现比较简单,具体参看 common/ob_mod_define.h/cpp
virtual void mod_malloc(const int64_t size, const int32_t mod_id);
virtual void mod_free(const int64_t size, const int32_t mod_id);
//以上两个函数用来修改模块的内存使用量的
另外,ObBaseMemPool中还有几个内存分配的统计量:
int64_t direct_allocated_block_num_; //请求的内存大于block的大小时,直接从系统new内存的次数
int64_t direct_allocated_mem_size_;// 请求的内存大于block的大小时,直接从系统new内存的总大小
ObFixedMemPool中一些关键的成员:
ObDLink used_mem_block_list_ ; // 使用块的链表
ObDLink free_mem_block_list_; //空闲块的链表
int64_t mem_block_size_; //定长内存池中标准的block大小
int64_t mem_fixed_item_size_; //标准block中item的大小
int64_t used_mem_block_num_; // 使用块的数量
int64_t free_mem_block_num_; //空闲块的数量
以下摘录部分代码进行说明,ObFixedMemPool的初始化函数声明:
int oceanbase::common::ObFixedMemPool::init(const int64_t fixed_item_size,
const int64_t item_num_each_block,
const ObMemPoolModSet *mod_set/* = NULL*/,
const bool numa_enabled/* = false*/)
系统中fixed_item_size 的默认大小是64K,item_num_each_block 是1,目前的实现这个参数只能设置为1.
这个函数主要是确定定长内存池的block大小和item大小:
int64_t real_block_size = (real_fixed_item_size+sizeof(MemPoolItemInfo))*item_num_each_block
+ sizeof(MemBlockInfo);
//item的大小,仅仅由输入参数决定
mem_fixed_item_size_ = real_fixed_item_size;
//memory block 的最小大小必须是OB_MINIMUM_MALLOC_MEM_SIZE == 64K + sizeof(MemBlockInfo) + sizeof(MemPoolItemInfo)
if (real_block_size <= OB_MINIMUM_MALLOC_MEM_SIZE)
{
mem_block_size_ = OB_MINIMUM_MALLOC_MEM_SIZE;
}
else
{
mem_block_size_ = real_block_size;
}
其中MemBlockInfo 是一个结构体:
struct MemBlockInfo
{
uint32_t magic_;
/// @property reference number
int32_t ref_num_; // 引用计数,当前实现一般为0或1
/// @property link all memblockinfo into list
oceanbase::common::ObDLink block_link_;//将MemBlockInfo串起来
/// @property size of this block
int64_t block_size_; //当前这个block的大小
/// @property buffer adress
char buf_[0];
};
MemPoolItemInfo 也是一个结构体:
struct MemPoolItemInfo
{
/// @property magic number
uint32_t magic_;
/// @property which module allocated this memory item
int32_t mod_id_;//哪个模块分配的
/// @property identify the memory was allocated from which MemBlockInfo
MemBlockInfo *mother_block_;//属于哪个block
/// @property buffer adress
char buf_[0];
};
ob_malloc()函数最终会调用ObFixedMemPool的以下函数进行内存分配:
void * oceanbase::common::ObFixedMemPool::malloc_(const int64_t nbyte,const int32_t mod_id, int64_t *got_size)
该函数首先判断系统是否设置了__OB_MALLOC_DIRECT__环境变量,如果设置了,直接new
//如果设置了 __OB_MALLOC_DIRECT__这个环境变量,那么就直接new,不使用内存
//池进行分配
if (malloc_directly())
{
result = new(std::nothrow) char[nbyte];
if (NULL == result)
{
result_errno = errno;
}
}
然后判断nbytes是否大于定长的item大小,则直接调用allocate_new_block()函数new一个新的block,并放入被使用链表。
/// 直接new,因为需要的字节比mem_fixed_item_size大,所以认为使用
//内存池的方式不对,会打ERROR log
if (NULL == result && result_errno == 0 && nbyte > mem_fixed_item_size_)
{
result =reinterpret_cast<char*>(allocate_new_block(block_info,item_info,block_size,mod_id,numa_enabled_));
if (result == NULL)
{
result_errno = errno;
}
else
{
tbsys::CThreadGuard guard(&pool_mutex_);
used_mem_block_list_.insert_next(block_info->block_link_);
/// statistic
mem_size_handled_ += block_info->block_size_;
used_mem_block_num_ ++;
// 直接new的非标准大小的内存,变量+1
direct_allocated_block_num_ ++;
direct_allocated_mem_size_ += block_info->block_size_;
size_malloc = block_info->block_size_;
}
if (block_size < mem_block_size_)
{
TBSYS_LOG(ERROR, "try to allocate variable sized memory from fixed memory pool "
"[nbyte:%ld,mem_fixed_item_size_:%ld]", nbyte, mem_fixed_item_size_);
}
}
如果nbytes一个block可以满足,从空闲列表中找一个block,并将block移除空闲链表,放入被使用链表
{
589 tbsys::CThreadGuard guard(&pool_mutex_);
590 /// allocate from free block list
591 if (result_errno == 0
592 && result == NULL
593 && !free_mem_block_list_.is_empty())
594 {
595 block_info = CONTAINING_RECORD(free_mem_block_list_.next(), MemBlockInfo,block_link_);
596 item_info = reinterpret_cast<MemPoolItemInfo *>(block_info->buf_);
597 block_info->block_link_.remove(); ///从当前链表中删除
598 used_mem_block_list_.insert_next(block_info->block_link_);///插入被使用链表
599 init_mem_pool_item_info(*item_info,block_info);
600 result = item_info->buf_;
601 /// 更新相关统计量
602 block_info->ref_num_ ++;
603 used_mem_block_num_ ++;
604 free_mem_block_num_ --;
605 size_malloc = block_info->block_size_;
606 item_info->mod_id_ = mod_id;
607 }
608 }
如果没有空闲的block,直接调用allocate_new_block()分配一个标准大小的block,放入被使用链表
/// allocated from system
611 if (result_errno == 0 && result == NULL)
612 {
613 result = reinterpret_cast<char*>(allocate_new_block(block_info,
614 item_info,mem_block_size_,mod_id,numa_enabled_));
615 if (result == NULL)
616 {
617 result_errno = errno;
618 }
619 else
620 {
621 tbsys::CThreadGuard guard(&pool_mutex_);
622 used_mem_block_list_.insert_next(block_info->block_link_);
623 /// statistic
624 used_mem_block_num_ ++;
625 result = item_info->buf_;
626 mem_size_handled_ += block_info->block_size_;
627 size_malloc = block_info->block_size_;
628 }
629 }
用户可以得到的内存大小放在got_size指向的内存中:
*got_size = size_malloc - static_cast<int64_t>(sizeof(MemBlockInfo) + sizeof(MemPoolItemInfo))
到此分配内存就结束了。
ob_free()最终会调用ObFixedMemPool的下面这个函数:
void oceanbase::common::ObFixedMemPool::free_(const void *ptr)
参数ptr是ob_malloc()返回的指针。
这个函数首先检查回收的块是不是标准的,如果是就放回空闲链表,如果不是,就置位need_free变量,后面判断这个变量释放内存给系统
//当前所在的block不是标准大小的block
691 else if (block_info->block_size_ != mem_block_size_)
692 {
693 mod_id = item_info->mod_id_;
694 buf = reinterpret_cast<char*>(block_info);
695 //回收这个block,则这个变量减一
696 used_mem_block_num_ --;
697 mem_size_handled_ -= block_info->block_size_;
698 //从链表中将这个结点移出
699 block_info->block_link_.remove();
700 //因为不是标准大小的block,所以这个变量减一
701 direct_allocated_block_num_ --;
702 // 与上面一样
703 direct_allocated_mem_size_ -= block_info->block_size_;
704 size_free = block_info->block_size_;
705 need_free = true;
706 }
707 else//标准大小的block
708 {
709 mod_id = item_info->mod_id_;
710 block_info->ref_num_ --;
711 size_free = block_info->block_size_;
712 //目前看当前实现,ref_num_要不是0要不是1
713 if (block_info->ref_num_ == 0)
714 {
715 block_info->block_link_.remove();
716 //插入空闲链表
717 free_mem_block_list_.insert_next(block_info->block_link_);
718 free_mem_block_num_ ++;
719 used_mem_block_num_ --;
720 }
721 }
判断need_free变量,决定是否释放内存给系统:
//非标准大小的block free的时候直接归还给系统,而不是放在空闲链表中
730 if (need_free)
731 {
732 if (numa_enabled_)
733 {
734 numa_free(block_info, size_free);
735 }
736 else
737 {
738 delete [] block_info;
739 }
740 block_info = NULL;
741 }
到这里,ob_free()的分析结束。
那么空闲链表和被使用链表中的内存是如何归还给系统的,主要都是通过调用下面这个函数,这个函数每次只释放一个block:
int64_t oceanbase::common::ObFixedMemPool::recycle_memory_block(ObDLink * &block_it,
468 bool check_unfreed_mem)
该函数归还block_it所在的block:
MemBlockInfo *info = CONTAINING_RECORD(block_it, MemBlockInfo, block_link_);
473 mem_size_handled_ -= info->block_size_;
474 memory_freed += info->block_size_;
475 block_it = info->block_link_.next();
476 //从链表中删除
477 info->block_link_.remove();
478 buf_allocated = reinterpret_cast<char*>(info);
479 if (info->ref_num_ != 0 )
480 {
481 used_mem_block_num_ --;
482 if (check_unfreed_mem)
483 {
484
488 }
489 }
490 else
491 {
492 free_mem_block_num_ --;
493 }
494
495 if (numa_enabled_)
496 {
497 numa_free(buf_allocated, info->block_size_);
498 }
499 else
500 {
501 delete [] buf_allocated;
502 }
注意:这里没有检查check_unfreed_mem这个开关,所以如果这个block正在被使用,也会被free,还给系统。
上面这个函数被下面这个函数调用,释放所有内存:
void oceanbase::common::ObFixedMemPool::clear(bool check_unfreed_mem)
513 {
514 tbsys::CThreadGuard guard(&pool_mutex_);
515 ObDLink *block_it = free_mem_block_list_.next();
516 while (block_it != &free_mem_block_list_)
517 {
518 recycle_memory_block(block_it, check_unfreed_mem);
519 }
520 block_it = used_mem_block_list_.next();
521 while (block_it != & used_mem_block_list_)
522 {
523 recycle_memory_block(block_it, check_unfreed_mem);
524 }
525 property_initializer();
526 }
至此,主要流程分配完成,可以看出,这个内存分配器还是不够完善的,如果每次分配的内存比较小,远小于64K的时候,会造成比较多的内存浪费,并且,clear内存的时候
没有对check_unfreed_mem做一些操作。