Redis在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc。在Redis中内存分配主要靠 zmalloc 实现,在zmalloc.c文件实现,在这个文件内还存在其他相关的操作函数。
void *zmalloc(size_t size); // 申请内存
void *zcalloc(size_t size); // 申请内存,并初始化为0
void *zrealloc(void *ptr, size_t size); // 重申请内存
void zfree(void *ptr); // 释放内存
1、zmalloc
void *zmalloc(size_t size) {
void *ptr = malloc(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
}
#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) { \
update_zmalloc_stat_add(_n); \
} else { \
used_memory += _n; \
} \
} while(0)
static size_t used_memory = 0;
通过malloc申请内存时,会多申请一个 PREFIX_SIZE 的大小。这里的 PREFIX_SIZE 就是用于保存内存长度的。内存申请后,将内存长度保存进内存的头部 PREFIX_SIZE 个字节的内存中。并通过update_zmalloc_stat_alloc 调整 use_memory 的记录。
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
这行代码的作用是判断_n
是不是sizeof(long)
的整数倍,如果不是则将_n
加上差值,使其成为整数倍。本质上是为了内存对齐的操作,实现内存的精准计算。我们需要注意的是,这段宏定义的作用只是为了use_memory
能够精准计算内存对齐之后的内存长度,而malloc
出来的内存已经实现了内存对齐和指针对齐。
2、PREFIX_SIZE
#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
分析zmalloc
函数发现存在HAVE_MALLOC_SIZE
。当定义了HAVE_MALLOC_SIZE
,PREFIX_SIZE
的长度就为0。那这个HAVE_MALLOC_SIZE
是什么来头?这个答案可以在zmalloc.h
找到:
#if defined(USE_TCMALLOC)
#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
#include <google/tcmalloc.h>
#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) tc_malloc_size(p)
#else
#error "Newer version of tcmalloc required"
#endif
#elif defined(USE_JEMALLOC)
#define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))
#include <jemalloc/jemalloc.h>
#if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) je_malloc_usable_size(p)
#else
#error "Newer version of jemalloc required"
#endif
#elif defined(__APPLE__)
#include <malloc/malloc.h>
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) malloc_size(p)
#endif
Redis允许用编译的时候使用第三方的内存分配方案,而这些内存分配方案往往自带存储内存长度的策略,如此一来,Redis就不必自己保存这个长度,反而浪费内存空间。使用第三方内存分配方案,会定义宏定义zmalloc_size
用于获取内存长度,比如这里的jemalloc:
#define zmalloc_size(p) je_malloc_usable_size(p)
3、zcalloc
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
}
在zcalloc
中,size
就已经是真实申请的内存。实现逻辑和zmalloc
差不多,唯一差别仅是申请内存使用的calloc
,内存会被初始化为0。
4、zrealloc
void *zrealloc(void *ptr, size_t size) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
#endif
size_t oldsize;
void *newptr;
if (size == 0 && ptr != NULL) {
zfree(ptr);
return 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+PREFIX_SIZE);
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
return (char*)newptr+PREFIX_SIZE;
#endif
}
重分配内存,主要看#else
部分。首先通过向低位偏移PREFIX_SIZE
个字节位置,拿到真实的内存地址,然后通过真实地址拿到旧内存长度。真实地址,可以通过realloc函数重分配内存,而旧内存长度则参与到use_memory
的内存长度计算中。
update_zmalloc_stat_free
的作用和update_zmalloc_stat_alloc
相反,是用来对use_memory
做减法操作的。
#define update_zmalloc_stat_free(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
atomicDecr(used_memory,__n); \
} while(0)
5、zfree
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
}
zfree
的时候获取到真实的地址之后计算use_memory
,并释放内存。