首先,列个计划吧。未来一周的blog分两个方向:一、内存管理,就基本使用,内存池,一些典型的内存管理算法如slab算法,tcmalloc的管理算法等做学习介绍;二、搜索引擎的基本概念、架构的学习;
malloc
分配的内存片段的指针,并将其释放,以便以后的程序或操作系统使用(实际上,一些 malloc
实现只能将内存归还给程序,而无法将内存归还给操作系统)。 概念:虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
碎片:由于虚拟内存使用了硬盘,硬盘上非连续写入的文件会产生磁盘碎片,因此一旦用于实现虚拟内存的文件或分区过于零碎,会加长硬盘的寻道时间,影响系统性能。
memcached的内存管理
memcached对内存的管理采用slab分配算法,根据powers of 2来将1MB的内存块划分成多个小内存块,其中这1MB的内存块称之为页。Powersof 2是2的n次方的意思,例如:2的0次方是1,2的1次方是2,2的2次方是4,2的3次方是8等等。以此类推,1MB的内存按2的n次方划分可以划分成20种不同的内存块。memcached是通过slabclass_t结构体来管理这些小内存块的,slabclass_t的定义如下面代码所示。
slots指针指向的是内存分配器回收的小内存块的数组,sl_total保存了回收器的容量,当回收器容量不足时,需要重新分配更大的内存来作为回收器, sl_curr是当前回收器回收到的位置,下一个回收的内存块就会放到这里。end_page_ptr保存的是当前的空闲内存块,end_page_free保存的是当前空闲块的数量,如果end_page_free等于0表示已经没有空闲内存块了,需要向系统申请一块新的内存页。slab_list保存的是申请的内存页, slabs保存的是已经申请的内存页数量。
typedef struct { unsigned int size; unsigned int perslab; void **slots; unsigned int sl_total; unsigned int sl_curr; void *end_page_ptr; unsigned int end_page_free; unsigned int slabs; void **slab_list; unsigned int list_size; unsigned int killing; } slabclass_t;
Slab算法相对比较简单和稳定,也具有较好的动态特性,但其最主要的问题是没有回收,对于我们多产品线混合运维的cache服务来说,其内存大小跨度大、变化大,这使得小内存被分配后很有可能就没人使用了,这样导致的内存浪费会比较严重。
spcache的内存和磁盘采用静态配置的方式进行管理,可以配置多个定长段,以及每个定长段的个数。这种方式避免了碎片的产生,对于单个产品线来说,能够有一些方法确定配置哪些级别长度更合适,但同样对于多产品线混合运维的cache服务,每个产品线为其单独进行配置使得维护极其复杂,统一配置又会导致空间浪费很严重。
tcmalloc是google实现的一套内存管理机制,它的特点是高效。Tcmalloc执行一次小对象的分配释放大约要50纳秒,glibc的malloc、free大约要300纳秒。Tcmalloc的内存缓存分线程和进程,内存分配时首先在线程缓存中找,找不到再去进程缓存中找,再找不到则让系统分配,释放时同样,先放回线程,线程空闲的较多则放会进程,这种方式对于多线程程序减少了很多内存分配释放的冲突代价。
Tcmalloc区分大对象和小对象,小于等于32K的为小对象,大对象直接使用页分配器(一个页是一个4K的对齐内存区域)从中央堆直接分配。即,一个大对象总是页对齐并占据整数个连续页。每个小对象的大小被映射到170个可分配的尺寸类别中的一个。一个线程缓存对每个尺寸都包括一个自由对象的链表。分配时只要映射到一个尺寸链表上,然后直接取,如果没有,再从该尺寸的中央自由链表(进程级)取若干个。大对象的分配由中央分配堆来处理,中央分配堆维护一个不同连续页个数的自由链表阵列,分配时取连续的页。
引用:
内存管理内幕:http://www.ibm.com/developerworks/cn/linux/l-memory/
百度百科-虚拟内存:http://baike.baidu.com/view/976.htm