redis 内存管理zmalloc


          

             redis封装的内部结构,脑子里要有这幅图,就差不多了。实际分配的内存比size要多。



redis的zmalloc函数

// 已经使用的内存,malloc函数增加,free减少这个值
static size_t used_memory = 0;
// 线程安全,其实没用;redis是单线程模型
static int zmalloc_thread_safe = 0;
// used_memory变量作为临界区,做同步
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;

// 默认内存溢出handler
static void zmalloc_default_oom(size_t size) {
	// 打印日志
    fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n",
        size);
	// 还是打印日志,到哪里 TODO
    fflush(stderr);
	// TODO
    abort();
}

static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;

void *zmalloc(size_t size) {
    void *ptr = malloc(size+PREFIX_SIZE);
	
	// 如果申请失败,一定是OOM
    if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
	// 统计用计数器增增加
    update_zmalloc_stat_alloc(zmalloc_size(ptr));
    return ptr;
#else
	// 使用4个字节记录申请内存字节数
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
	// 返回实际内存指针
    return (char*)ptr+PREFIX_SIZE;
#endif
       1 首先把和多线程相关的去掉,先忽略。

       2 HAVE_MALLOC_SIZE是什么意思zmalloc_size 内容是什么?

http://www.petermao.com/redis/78.html 这篇文章 对 HAVE_MALLOC_SIZE 有一行注释,“另外对于 apple系统,可以用malloc_size(redis_malloc_size是对它的封装)取得指针所指向的内存块大小,因此就不需要手动保存大小了。” 据此明了,redis 实现了类似apple的功能,使用申请的内存的前一段PREFIX_SIZE,保存真正申请(可以使用)的内存字节数。

        zmalloc_size函数的注释,对于一些不支持zmalloc_size的系统这种情况,redis在每次分配内存的时候使用前几个字节保存实际使用的字节数。

/* Provide zmalloc_size() for systems where this function is not provided by
 * malloc itself, given that in that case we store a header with this
 * information as the first bytes of every allocation. */
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr) {
    void *realptr = (char*)ptr-PREFIX_SIZE;
    size_t size = *((size_t*)realptr);
    /* Assume at least that all the allocations are padded at sizeof(long) by
     * the underlying allocator. */
    if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
    return size+PREFIX_SIZE;
}
#endif

    3 PREFIX_SIZE 记录一次分配内存,使用内存的字节数,他的大小怎么定义的?

http://blog.csdn.net/jinjinstudy/article/details/18189567

        PREFIX_SIZE用来记录malloc已分配到的内存大小  
        tc_malloc/je_malloc/Mac平台分别采用tc_malloc_size/malloc_size/je_malloc_usable_size(不需要单独开空间计算得到的内存大小,PREFIX_SIZE值置为0)  
        linux和sun平台分别采用sizeof(size_t)=8字节和sizeof(long long)定长字段记录,所以要记录分配空间的大小  

#ifdef HAVE_MALLOC_SIZE
    #define PREFIX_SIZE (0)
#else
    #if defined(__sun) || defined(__sparc) || defined(__sparc__)
        #define PREFIX_SIZE (sizeof(long long))
    #else
        #define PREFIX_SIZE (sizeof(size_t))
    #endif
#endif
       

       简单来讲,可以把PREFIX_SIZE看作一个size_t的大小(一般是8字节)。它的主要作用是来记录本次分配的内存空间大小。对于不同平台来说,PREFIX_SIZE的值是不同的。

  • 如果平台内存分配策略,已经使用前几个字节记录大小(HAVE_MALLOC_SIZE),PREFIX_SIZE = 0就不用再额外申请内存。
  • 如果是sun的服务器,则分配一个8个字节当前缀 sizeof(long long)。
  • 其他的使用size_t的大小8个字节。
     4  zmalloc_size函数字节填充pading。翻译下注释:每次内存分配都是8字节的整数倍,如果不够进pading凑足8的整数倍。

    /* Assume at least that all the allocations are padded at sizeof(long) by
     * the underlying allocator. */
    if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
     例如 size = 15 ,二进制表示 11111,sizeof(long) - 1 = 7,二进制表示111, 按位与15 & 7,结果是111,不等于0;所以,size +=

8 - 7,结果是16.

     5 当使用tcmalloc库/jemalloc库的时候,显式覆盖malloc/calloc/realloc/free的方法  

/* Explicitly override malloc/free etc when using tcmalloc. */
#if defined(USE_TCMALLOC)
#define malloc(size) tc_malloc(size)
#define calloc(count,size) tc_calloc(count,size)
#define realloc(ptr,size) tc_realloc(ptr,size)
#define free(ptr) tc_free(ptr)
#elif defined(USE_JEMALLOC)
#define malloc(size) je_malloc(size)
#define calloc(count,size) je_calloc(count,size)
#define realloc(ptr,size) je_realloc(ptr,size)
#define free(ptr) je_free(ptr)
#endif
    6 增加内存和释放内存时used_memory计数器

#if defined(__ATOMIC_RELAXED)	// 平台相关define
#define update_zmalloc_stat_add(__n) __atomic_add_fetch(&used_memory, (__n), __ATOMIC_RELAXED)
#define update_zmalloc_stat_sub(__n) __atomic_sub_fetch(&used_memory, (__n), __ATOMIC_RELAXED)
#elif defined(HAVE_ATOMIC)		// 平台相关define
#define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n))
#define update_zmalloc_stat_sub(__n) __sync_sub_and_fetch(&used_memory, (__n))
#else

// 已使用内存 增加delta
#define update_zmalloc_stat_add(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory += (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)	// 只执行一次

// 已使用内存 减少delta
#define update_zmalloc_stat_sub(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory -= (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)

#endif

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
	// 内存对齐
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    if (zmalloc_thread_safe) { \	// 多线程case使用
        update_zmalloc_stat_add(_n); \
    } else { \
        used_memory += _n; \
    } \
} while(0)

#define update_zmalloc_stat_free(__n) do { \
    size_t _n = (__n); \
	// 内存对齐
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    if (zmalloc_thread_safe) { \
        update_zmalloc_stat_sub(_n); \
    } else { \
        used_memory -= _n; \
    } \
} while(0)

     7 calloc函数,他和malloc有什么不同?

void *zcalloc(size_t size) {
    void *ptr = calloc(1, size+PREFIX_SIZE);

    if (!ptr) zmalloc_oom_handler(size);
#ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_alloc(zmalloc_size(ptr));
    return ptr;
#else
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    return (char*)ptr+PREFIX_SIZE;
#endif
}

     http://blog.csdn.net/firecityplans/article/details/4490124/

     void *malloc(unsigned size)//动态申请size个字节的内存空间;功能:在内存的动态存储区中分配一块长度为" size" 字节的连续区域。函数的返回值为该区域的首地址。。(类型说明符*)表示把返回值强制转换为该类型指针。

     (void *)calloc(unsigned n,unsigned size)//      用于向系统动态申请n个, 每个占size个字节的内存空间; 并把分配的内存全都初始化为零值。函数的返回值为该区域的首地址

     (void *)realloc(void *p,unsigned size)//将指针p所指向的已分配内存区的大小改为size

区别:两者都是动态分配内存。主要的不同是malloc不初始化分配的内存,已分配的内存中可以是任意的值. calloc 初始化已分配的内存为0。次要的不同是calloc返回的是一个数组,而malloc返回的是一个对象。


      8  zrealloc 函数。realloc的功能是先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。
/**
   重新分配内存;外部传入的指针地址是字符串地址;
*/
void *zrealloc(void *ptr, size_t size) {
#ifndef HAVE_MALLOC_SIZE
    // ptr - PREFIX_SIZE 的地址
    void *realptr;
#endif
    size_t oldsize;
    void *newptr;
	
	// 如果ptr == null,重新分配一段内存
    if (ptr == NULL) return zmalloc(size);
#ifdef HAVE_MALLOC_SIZE
    oldsize = zmalloc_size(ptr);
    newptr = realloc(ptr,size);
    if (!newptr) zmalloc_oom_handler(size);

    update_zmalloc_stat_free(oldsize);
    update_zmalloc_stat_alloc(zmalloc_size(newptr));
    return newptr;
#else
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    newptr = realloc(realptr,size+PREFIX_SIZE);
    if (!newptr) zmalloc_oom_handler(size);

    *((size_t*)newptr) = size;
    update_zmalloc_stat_free(oldsize);
    update_zmalloc_stat_alloc(size);
	// 返回值是真正使用的字符串地址
    return (char*)newptr+PREFIX_SIZE;
#endif
}

          补说明,realloc函数的说明

realloc(void *__ptr, size_t __size):更改已经配置的内存空间,即更改由malloc()函数分配的内存空间的大小。


如果将分配的内存减少,realloc仅仅是改变索引的信息。


如果是将分配的内存扩大,则有以下情况:
1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
2)如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
3)如果申请失败,将返回NULL,此时,原来的指针仍然有效。

注意:如果调用成功,不管当前内存段后面的空闲空间是否满足要求,都会释放掉原来的指针,重新返回一个指针,虽然返回的指针有可能和原来的指针一样,即不能再次释放掉原来的指针。


       9 free函数,系统中除了分配请求大小的内存外,还在该内存块头部保存了该内存块的大小,这样,释放的时候可以通过该大小找到该内存块的起始位置:

void zfree(void *ptr) {
#ifndef HAVE_MALLOC_SIZE
    void *realptr;
    size_t oldsize;
#endif

    if (ptr == NULL) return;
#ifdef HAVE_MALLOC_SIZE
    update_zmalloc_stat_free(zmalloc_size(ptr));
    free(ptr);
#else
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    free(realptr);
#endif
}

         10 函数 获取已使用内存量,有并发的场景,前后加锁。

size_t zmalloc_used_memory(void) {
    size_t um;

    if (zmalloc_thread_safe) {
#if defined(__ATOMIC_RELAXED) || defined(HAVE_ATOMIC)
        um = update_zmalloc_stat_add(0);
#else
        pthread_mutex_lock(&used_memory_mutex);
        um = used_memory;
        pthread_mutex_unlock(&used_memory_mutex);
#endif
    }
    else {
        um = used_memory;
    }

    return um;
}

参考:

   http://blog.csdn.net/guodongxiaren/article/details/44747719

   http://blog.csdn.net/guodongxiaren/article/details/44783767

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值