Redis源码分析(1)内存管理

Redis在zmalloc.h和zmalloc.c实现了底层的内存管理,代码很简洁。
Redis的内存管理提供了以下几个函数:


// 申请内存,封装malloc
void *zmalloc(size_t size);
// 申请内存并初始化为0,封装calloc
void *zcalloc(size_t size);
// 调整内存大小,封装realloc
void *zrealloc(void *ptr, size_t size);
// 释放内存,封装free
void zfree(void *ptr);
// 拷贝字符串
char *zstrdup(const char *s);
/***********************以下都是用于控制程序内存*******************************/
// 获取已使用的内存大小(猜测用于内存管理LRU等)
size_t zmalloc_used_memory(void);
// 启用线程安全
void zmalloc_enable_thread_safeness(void);
// 设置处理out-of-memory的函数
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
// 获取碎片化比例,rss:是常驻内存集(Resident Set Size),表示该进程分配的内存大小
float zmalloc_get_fragmentation_ratio(size_t rss);
// 获取程序占用的内存(常驻内存集Resident Set Size),除了程序分配的内存,还包括进程运行本身需要的内存、内存碎片等,但是不包括虚拟内存
size_t zmalloc_get_rss(void);

size_t zmalloc_get_private_dirty(void);
size_t zmalloc_get_smap_bytes_by_field(char *field);
void zlibc_free(void *ptr);
WIN32_ONLY(void zmalloc_free_used_memory_mutex(void);)

// 获取指定指针申请的内存空间大小
size_t zmalloc_size(void *ptr);

要分析redis的内存管理,先看内存是如何申请的:

// 申请一段内存空间
void *zmalloc(size_t size) {
    // 申请内存,多申请了PREFIX_SIZE,PREFIX_SIZE大小是sizeof(size_t)
    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
    // 将长度写入头部,前PREFIX_SIZE个byte用于存储申请的内存大小
    *((size_t*)ptr) = size;
    // 更新内存使用量统计,添加使用内存量计数
    update_zmalloc_stat_alloc(size+PREFIX_SIZE);
    // 返回内存数据区起始地址
    return (char*)ptr+PREFIX_SIZE;
#endif
}

redis使用zmalloc申请的内存结构如下:

----------------------------------------------------------------
|  数据区长度 |       数据区     |
----------------------------------------------------------------
↑         ↑
实际头地址    返回的指针地址
在这里插入图片描述
redis通过**update_zmalloc_stat_alloc、update_zmalloc_stat_free、**这个函数调整当前内存使用量的统计:

#define update_zmalloc_stat_add(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory += (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)

#define update_zmalloc_stat_sub(__n) do { \
    pthread_mutex_lock(&used_memory_mutex); \
    used_memory -= (__n); \
    pthread_mutex_unlock(&used_memory_mutex); \
} while(0)

#define update_zmalloc_stat_alloc(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(PORT_LONG)-1)) _n += sizeof(PORT_LONG)-(_n&(sizeof(PORT_LONG)-1)); \
    if (zmalloc_thread_safe) { \
        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(PORT_LONG)-1)) _n += sizeof(PORT_LONG)-(_n&(sizeof(PORT_LONG)-1)); \
    if (zmalloc_thread_safe) { \
        update_zmalloc_stat_sub(_n); \
    } else { \
        used_memory -= _n; \
    } \
} while(0)

static size_t used_memory = 0;

这个有个要点,计算机是如何分配内存的?例如我们用malloc(1)申请一个byte的内存,会占用多大的空间?

在64位系统,如果申请内存为1~24字节,系统内存消耗32字节,当申请25字节的内存时,系统内存消耗48字节。而对于32位系统,申请内存为1~12字节时,系统内存消耗为16字节,当申请内存为13字节时,系统内存消耗为24字节。(这段在linux下适用,是实际占用的内存,可用malloc_usable_size测试,但与下文的无关)

redis给了一个计算申请内存大小的公式:

#define PORT_LONG size_t
// 获取使用的内存大小,将n调整为sizeof(PORT_LONG)的整数倍
int get_memory_size(size_t _n)
{
    if (_n & (sizeof(PORT_LONG) - 1))
        _n += sizeof(PORT_LONG) - (_n & (sizeof(PORT_LONG) - 1));
    return _n;
}

redis是如何获取申请的内存大小的:

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;
}

used_memory是静态变量,用于保存申请的内存大小,每次内存调整都会修改其值。

redis通过封装malloc、zalloc、realloc、free实现了自己的内存管理,主要是定义了自己的内存结构并可以统计内存使用量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值