内存管理

背景

内存管理不恰当,会有造成大量内存碎片、内存分配效率低、内存实际使用率低。在程序运行中,这些问题会导致程序无法持久正常运行,于服务器技术而言更是如此。

目标

内存管理可以分为三个层次,自底向上分别是:

  • 操作系统内核的内存管理
  • 使用系统调用维护的内存管理算法
  • 在上一步基础上,根据应用程序本身的程序特性进行优化, 比如使用引用计数、内存池方式等

本文我们主要讨论第二步的使用系统调用维护的内存管理算法

对于需要持久运行的程序而言,内存管理十分重要,一个优秀的内存管理策略应该具有如下特性:

  • 分配速度尽可能快
  • 内存碎片尽可能少
  • 内存实际使用率高

memcached内存管理机制

memcached是一个知名的高效的分布式内存cache,默认使用SlabAllocation机制管理内存,其主要思想是按照预先规定的大小,将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录,以完全解决内存碎片问题。

Slab和Chunk

memcached内存结构图

slab是memcached一次申请内存的最小单位。memcached启动时使用参数-m指定其可用内存,但并不是启动时所有的内存就全部分配出去了,只有在需要时才会去申请,而且每次申请一定是一个slab。Slab的大小固定为1M(1048576 Byte)。

一个slab由若干个大小相等的chunk组成。每个chunk中都保存了一个item结构体、一对key和value。

虽然在同一个slab中chunk的大小相等的,但是在不同的slab中chunk的大小并不一定相等,在memcached中按照chunk的大小不同,可以把slab分为很多种类(class)。

在启动memcached的时候可以通过-vv来查看slab的种类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ memcached -vv
slab class 1: chunk size 80 perslab 13107
slab class 2: chunk size 104 perslab 10082
slab class 3: chunk size 136 perslab 7710
slab class 4: chunk size 176 perslab 5957
slab class 5: chunk size 224 perslab 4681
slab class 6: chunk size 280 perslab 3744
slab class 7: chunk size 352 perslab 2978
slab class 8: chunk size 440 perslab 2383
slab class 9: chunk size 552 perslab 1899
slab class 10: chunk size 696 perslab 1506
slab class 11: chunk size 872 perslab 1202
slab class 12: chunk size 1096 perslab 956
slab class 13: chunk size 1376 perslab 762
.
.
.

向memcached添加item时,memcached首先会根据item大小,根据chunk size向上取整,来选择接近的slab class。例如,item大小为156,向上取最小的chunk size为176.

memcached采取SlabAllocation策略,一次向系统申请1M空间内存作为slab。slab根据其chunk size分割成等大的内存块chunk,每个chunk都可以存储一个item。memcached内存分配做到了一次申请大量空间,分割多块给小对象多次使用,大大降低了向操作系统申请内存的时间,大部分内存管理机制都会用到这个策略。

其次,memcached将slab按chunk size分组,将每一个slab内部按chunk size分割成固定大小的内存块。固定大小的chunk,回收之后还可以用于下一次item的存储,内存的分布情况不会变的错综复杂,有利于减少内存碎片。chunk size递增策略,每一次item都向上取到最接近的chunk size对应的slab来存储,有利于提高其实际内存利用率

Redis内存管理机制

Redis默认使用jemalloc作为内存分配器。jemalloc 是一个通用的 malloc(3)实现,它强调了分段回避和可伸缩并发支持。jemalloc 在 2005 年首次作为 FreeBSD libc 分配器使用,2010年,jemalloc 的功能延伸到如堆分析和监控/调优等。现代的 jemalloc 版本依然集成在 FreeBSD 中。

jemalloc机制相对memcached的SlabAllocation策略来说比较复杂,这里不做详细介绍,详情可参考本文列出的参考文档。

类似的,对jemalloc策略总结如下:

  • 一次向系统申请大内存4M(chunk),供之后多次使用
  • 将控制的内存分成不同大小的小内存块(bins),重复使用
  • 存放对象时,向上找到最接近size的bin对应的空闲内存块来存储
  • 每个线程有自己的独立空间tcache,将内存划分为arena供线程使用,减少线程间的互斥访问

前三条策略的思想与memcached的SlabAllocationc策略所蕴含的思想一致,第四条策略是针对多线程加速的优化。

参考资料

内存优化总结:ptmalloc、tcmalloc和jemalloc

memcached的内存管理机制

jemalloc和内存管里

A Scalable Concurrent malloc(3) Implementation for FreeBSD

jemalloc内存分配器详解