Redis默认的内存分配器采用jemalloc,可选的分配器还有:glibc、tcmalloc。内存分配器为了更好地管理和重复利用内存,分配内存策略一般采用固定范围的内存块进行分配。例如jemalloc在64位系统中将内存空间划分为:小、大、巨大三个范围。每个范围内又划分为多个小的内存单元,如下所示:
- 小:[8 byte],[16 byte, 32 byte, 48 byte, …, 128 byte], [192 byte, 256 byte, …, 512 btye], [768 byte, 1024 byte, …, 3849 byte]
- 大:[4KB, 8KB, 12KB, …, 4072KB]
- 巨大:[4MB, 8MB, 12MB, …]
zmalloc.h分析:
源文件zmalloc.h的宏定义显示了对内存分配器的选择:
我们可以看到对USE_TCMALLOC的宏定义,其中__xstr是宏值字符串化。如果定义了USE_TCMALLOC,也就是使用TCMALLOC,则定义ZMALLOC_LIB为tcmalloc-主版本号.次版本号,并且引入google/tcmalloc.h文件。如果版本是在次版本大于1.6或主版本号大于1的情况下,定义HAVE_MALLOC_SIZE为1,并定义zmalloc_size§为tc_malloc_size§。否则会输出需要更新的版本。
HAVE_MALLOC_SIZE宏定义是对zmalloc.c提供函数是否有zmalloc_size函数的控制。
可以看到对USE_JEMALLOC的宏定义。如果定义了USE_JEMALLOC,也就是使用JEMALLOC,则定义JEMALLOC_LIB为jemalloc-主版本号.次版本号,并且引入jemalloc/jemalloc.h文件。如果版本是在次版本大于2.1或主版本号大于2的情况下,定义HAVE_MALLOC_SIZE为1,并定义zmalloc_size§为je_malloc_usable_size§。否则会输出需要更新的版本。
如果jemalloc和tcmalloc都没有使用就用glibc的库函数
zmalloc.h包含的函数列表:
zmalloc.c分析:
这是在源文件中对函数调用进行替换,以保证zmalloc是对内存函数的包装。
zmalloc针对used_memory的多线程保护的处理:used_memory是使用的内存量,而used_memory_mutex是针对used_memory变量的保护锁。zmalloc_thread_safe是对zmalloc线程安全性的开关。
当使用原子操作时,就使用__sync_add_and_fetch和__sync_sub_and_fetch。否则就使用线程锁,update_zmalloc_stat_add和update_zmalloc_stat_sub就是提供的对加锁解锁操作的封装。
如果zmalloc_thread_safe开启就使用线程安全的操作,如果不开启就是一般操作。而90行的代码是确保内存对齐,就是保证对齐边缘是long的大小。
对函数的简要分析:如果无法得到需要的内存,将会调用zmalloc_oom_handler函数,该函数将打印出错信息。
通过HAVE_MALLOC_SIZE来决定是否提供zmalloc_size函数,如果没有提供,那么在其他函数实现时,需要写提取内存大小的代码。内存的大小是存放在分配的内存的首地址中。
在源文件中对HAVE_MALLOC_SIZE的处理:如果定义了HAVE_MALLOC_SIZE宏定义,则PREFIX_SIZE定义为0。否则根据处理器架构来定义PREFIX_SIZE。
源文件中还有一部分比较重要的函数是获取RSS信息
这部分函数有多种实现方式:提取/proc中的信息、taskinfo函数和zmalloc实现的统计方法zmalloc_used_memory()
这是从/proc文件系统中提取内存rss,使用页面数乘页大小。
task_info函数
zmalloc实现的统计方法
zmalloc_get_private_dirty的实现:使用/poc/self/smaps的统计信息
关于tcmalloc的参考
https://yq.aliyun.com/articles/6045 https://zhuanlan.zhihu.com/p/29216091