memcached(三)内存管理

memcached使用预申请的方式来管理内存的分配,从而避免内存碎片化的问题。如果采用mallo和free来动态的申请和销毁内存,必然会产生大量的内存碎片。

基本知识

slab:内存块是memcached一次申请内存的最小单元,在memcached中一个slab的默认大小为1M;

slabclass:特定大小的chunk的组。

chunk:缓存的内存空间,一个slab被划分为若干个chunk;

item:存储数据的最小单元,每一个chunk都会包含一个item;

factor:增长因子,默认为1.25,相邻slab中的item大小与factor成比例关系;

基本原理

memcached使用预分配方法,避免频繁的调用malloc和free;

memcached通过不同的slab来管理不同chunk大小的内存块,从而满足存储不同大小的数据。

slab的申请是通过在使用item时申请slab大小的内存空间,然后再把内存切割为大小相同的item,挂在到slab的未使用链表上。

过期和被删除item并不会被free掉,memcached并不会删除已经分配的内存;

Memcached会优先使用已超时的记录空间,通过LRU算法;

memcached使用lazy expiration来判断元素是否过期,所以过期监视上不会占用cpu时间。

源码分析

下面主要分析memcached的内存申请和存储相关代码。

item

item是key/value的存储单元。

typedef struct _stritem {
struct _stritem *next; /* 前后指针用于在链表slab->slots中连接前后数据 */
struct _stritem *prev;
struct _stritem *h_next; /* hash chain next */
rel_time_t time; /* 最后一次访问时间 */
rel_time_t exptime; /* 过期时间 */
int nbytes; /* 数据大小 */
unsigned short refcount; /* 引用次数 */
uint8_t nsuffix; /* suffix长度 */
uint8_t it_flags; /* ITEM_* above */
uint8_t slabs_clsid;/* 所有slab的id */
uint8_t nkey; /* key长度 */
/* this odd type prevents type-punning issues when we do
* the little shuffle to save space when not using CAS. */
union {
uint64_t cas;
char end;
} data[]; /* cas|key|suffix|value */
} item;

slab初始化

void slabs_init(const size_t limit, const double factor, const bool prealloc) {
int i = POWER_SMALLEST - ;
unsigned int size = sizeof(item) + settings.chunk_size; /* 得到每一个item的大小 */ mem_limit = limit; if (prealloc) { /* 预分配一块内存 */
...
} memset(slabclass, , sizeof(slabclass)); /* 把slabclass置为0,slabclass是一个slab数组,存储所有slab的信息 */ while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) { /* 循环初始化每一个slab的内容,保证slab中item的size小于max_size/factor */
/* Make sure items are always n-byte aligned */
if (size % CHUNK_ALIGN_BYTES) /* 用于内存对齐 */
size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); slabclass[i].size = size; /* 初始化slabclass中item的大小 */
slabclass[i].perslab = settings.item_size_max / slabclass[i].size; /* 初始化每个slab中item的数量 */
size *= factor; /* item的大小随factor逐渐增大 */
...
}
/* 初始化最后一个slab,大小为最大的max_size,只有一个item */
power_largest = i;
slabclass[power_largest].size = settings.item_size_max;
slabclass[power_largest].perslab = ;
...
}

从源码中,可以看出来同一个slab中所有的item的大小都是固定的,

申请slab内存

static void *do_slabs_alloc(const size_t size, unsigned int id) {
slabclass_t *p;
void *ret = NULL;
item *it = NULL; if (id < POWER_SMALLEST || id > power_largest) { /* 判断id是否合法 */
MEMCACHED_SLABS_ALLOCATE_FAILED(size, );
return NULL;
} p = &slabclass[id]; /* 获取slab */
assert(p->sl_curr == || ((item *)p->slots)->slabs_clsid == ); /* fail unless we have space at the end of a recently allocated page,
we have something on our freelist, or we could allocate a new page */
if (! (p->sl_curr != || do_slabs_newslab(id) != )) { /*如果sl_curr为0,没有剩余的item,那么就执行do_slabs_newslab申请内存空间*/
/* We don't have more memory available */
ret = NULL;
} else if (p->sl_curr != ) { /* 如果有未使用的空间,则获取该item,并从slots链表中删除该item */
/* return off our freelist */
it = (item *)p->slots;
p->slots = it->next;
if (it->next) it->next->prev = ;
p->sl_curr--;
ret = (void *)it;
}
...
return ret;
}

sl_curr来判断是否存在未使用的内容空间,如果不存在需要调用do_slabs_newslab来申请slab空间。

static int do_slabs_newslab(const unsigned int id) {
slabclass_t *p = &slabclass[id];
int len = settings.slab_reassign ? settings.item_size_max
: p->size * p->perslab;
char *ptr;
/* 1. 判断是否超过内存限制
2. 判断是否申请过内存空间
3. 如果没有申请过,则申请slab->size*slab->perslab大小的整块内存
4.如果申请过,调用grow_slab_list来扩大slab大小 */
if ((mem_limit && mem_malloced + len > mem_limit && p->slabs > 0) ||
(grow_slab_list(id) == 0) ||
((ptr = memory_allocate((size_t)len)) == 0)) { MEMCACHED_SLABS_SLABCLASS_ALLOCATE_FAILED(id);
return 0;
} memset(ptr, 0, (size_t)len);
split_slab_page_into_freelist(ptr, id); /* 把申请的内存分配到slots链表中 */ p->slab_list[p->slabs++] = ptr;
mem_malloced += len;
MEMCACHED_SLABS_SLABCLASS_ALLOCATE(id); return ;
}

申请空间后,需要通过split_slab_page_into_freelist函数把申请的内存空间分配到未使用的链表中。

static void split_slab_page_into_freelist(char *ptr, const unsigned int id) {
slabclass_t *p = &slabclass[id];
int x;
for (x = ; x < p->perslab; x++) { /* 循环分配内存 */
do_slabs_free(ptr, , id);
ptr += p->size;
}
} static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {
slabclass_t *p;
item *it;
...
p = &slabclass[id];
/* 获取内存指针,把item块挂在到slots链表中,增加sl_curr */
it = (item *)ptr;
it->it_flags |= ITEM_SLABBED;
it->prev = ;
it->next = p->slots;
if (it->next) it->next->prev = it;
p->slots = it; p->sl_curr++;
p->requested -= size;
return;
}

获取适当大小的item

在do_item_alloc中,调用了slabs_clsid来获取适合存储当前元素的slab id。

unsigned int slabs_clsid(const size_t size) {
int res = POWER_SMALLEST; if (size == )
return ;
while (size > slabclass[res].size) /* 遍历slabclass来找到适合size的item */
if (res++ == power_largest) /* won't fit in the biggest slab */
return ;
return res;
}

优缺点

内存预分配可以避免内存碎片以及避免动态分配造成的开销。

内存分配是由冗余的,当一个slab不能被它所拥有的chunk大小整除时,slab尾部剩余的空间就会被丢弃。

由于分配的是特定长度的内存,因此无法有效地利用所有分配的内存,例如如果将100字节的数据存储在128字节的chunk中,会造成28字节的浪费。

memcache(三)内存管理的更多相关文章

  1. memcache的内存管理机制

    Memcache使用了Slab Allocator的内存分配机制:按照预先规定的大小,将分配的内存分割成特定长度的块,以完全解决内存碎片问题Memcache的存储涉及到slab,page,chunk三 ...

  2. memcache 的内存管理介绍和 php实现memcache一致性哈希分布式算法

    1 网络IO模型 安装memcached需要先安装libevent Memcached是多线程,非阻塞IO复用的网络模型,分为监听主线程和worker子线程,监听线程监听网络连接,接受请求后,将连接描 ...

  3. How Javascript works (Javascript工作原理) (三) 内存管理及如何处理 4 类常见的内存泄漏问题

    个人总结: 1.两种垃圾回收机制: 1)引用标记算法:如果检测到一个对象没有被引用了,就清除它. ***这种算法不能处理循环引用的情况*** 2)标记—清除算法:从根(全局变量)开始向后代变量检测,任 ...

  4. memcache的内存管理探微

    slab分配器:http://blog.csdn.net/luotuo44/article/details/42737181 hash操作  :http://blog.csdn.net/luotuo4 ...

  5. OC-手动内存管理

    一.为什么要进行内存管理 •移动设备的内存极其有限,每个app所能占用的内存是有限制的 • •下列行为都会增加一个app的内存占用 Ø创建一个OC对象 Ø定义一个变量 Ø调用一个函数或者方法 • •当 ...

  6. 分布式缓存技术memcached学习(三)——memcached内存管理机制

    几个重要概念 Slab memcached通过slab机制进行内存的分配和回收,slab是一个内存块,它是memcached一次申请内存的最小单位,.在启动memcached的时候一般会使用参数-m指 ...

  7. MemCache中的内存管理详解

    MC的内存管理机制 1.内存的碎片化 当我们使用C语言或者其他语言进行malloc(申请内存),free(释放内存)等类似的命令操作内存的时候, 在不断的申请和释放的过程中,形成了一些很小的内存片段, ...

  8. Android 之 内存管理-查看内存泄露(三)

    概述 在android的开发中,要时刻主要内存的分配和垃圾回收,因为系统为每一个dalvik虚拟机分配的内存是有限的,在google的G1中,分配的最大堆大小只有16M,后来的机器一般都为24M,实在 ...

  9. block没那么难(三):block和对象的内存管理

    本系列博文总结自<Pro Multithreading and Memory Management for iOS and OS X with ARC> 在上一篇文章中,我们讲了很多关于 ...

随机推荐

  1. 121. Best Time to Buy and Sell Stock (一) leetcode解题笔记

    121. Best Time to Buy and Sell Stock Say you have an array for which the ith element is the price of ...

  2. ORACLE RAC 11G 更改 /etc/hosts文件

    来自官方文档:()Can I change the public hostname in my Oracle Database 10g Cluster using Oracle Clusterware ...

  3. 父窗口,子窗口之间的JS"通信"方法

    今天需要在iframe内做一个弹窗,但使用弹窗组件的为子窗口,所以弹窗只在子窗口中显示掩膜层和定位,这样不符合需求. 后来晓勇哥指点,了解到一个以前一直没关注到的东西,每个窗口的全局变量,其实都存在对 ...

  4. oracle flashback功能

    2). 检查Flashback 功能, 缺省时功能是关闭的. SQL> select name, current_scn, flashback_on from v$database; NAME  ...

  5. IIS7.5 由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面

    IIS7.5中将一网站应用程序池托管管道模式改为经典后,网站页面打不开,错误信息: 引用内容 HTTP 错误 404.2 - Not Found由于 Web 服务器上的“ISAPI 和 CGI 限制” ...

  6. AngularJS 页面基本操作

    一.避免预先加载angular模板语法 <style> [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, ...

  7. spring事务源码研读1

    转载摘录自:Spring事务源码分析(一)Spring事务入门 有时为了保证一些操作要么都成功,要么都失败,这就需要事务来保证. 传统的jdbc事务如下: @Test public void test ...

  8. java线程池初步理解

    多线程基础准备 进程:程序的执行过程,持有资源和线程 线程:是系统中最小的执行单元,同一个进程可以有多个线程,线程共享进程资源 线程交互(同步synchronized):包括互斥和协作,互斥通过对象锁 ...

  9. bzoj1179(Atm)

    ---恢复内容开始--- 1179: [Apio2009]Atm Time Limit: 15 Sec  Memory Limit: 162 MB Description Input 第一行包含两个整 ...

  10. Python 学习---------Day3

    第七章 字符串单双引号字符串是一样的用转义序列代表特殊字节字符串抑制转义myfile=open(r'C:\new\text.dat','w')三重引号编写多行字符串块字符串更大的编码集std(u'sp ...