参考:<<Redis设计与实现>>
- 注:这本书是基于Redis3.0版本写的,和后面的版本有点差异
一、内存回收
C语言不具备自动内存回收功能,所以Redis在自己的对象系统中构建了一个引用计数(reference counting)计数实现的内存回收机制。
通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当时候自动释放对象并进行内存回收。
每个对象的引用计数信息由redisObject结构的refcount属性记录:
typedef struct redisObject {
// ...
// 引用计数
int refCount;
// ...
} robj;
对象的引用计数信息会随着对象的使用状态而不断变化:
- 在创建一个新对象时,引用计数的值会被初始化为1
- 当对象被一个新程序使用时,它的引用计数值会被+1
- 当对象不再被一个程序使用时,它的引用计数值会被-1
- 当对象的引用计数值变为0时,对象所占的内存会被释放
对象的整个生命周期可以划分为:创建对象、操作对象、释放对象三个阶段。
示例:
展示一个字符串对象从创建到释放的整个过程:
// 创建一个字符串对象s,对象的引用计数为1
robj *s = createStringObject(...);
// 对象s执行各种操作...
// 将对象s的引用计数-1,使得对象的引用计数变为0,导致对象被释放
defrRefCount(s);
其他不同类型的对象声明周期也类似。
二、对象共享
对象的引用计数属性除了用于计数内存回收机制外,还带有对象共享的作用。
在Redis中,多个键共享同一个值对象需要以下两个步骤:
- 将数据库键的值指针指向一个现有的值对象
- 将被共享的值对象的引用计数+1
示例:
键A、B都创建了一个包含整数值100的字符串对象作为值对象,此时这个值对象将会被A、B共享,对象的引用计数就会变成2,其他属性没有变化。参考下图说明:
目前来说,Redis在初始化服务器时,创建一万个字符串对象,这些对象包含了从0-9999的所有整数值,当服务器需要用到值为0-9999的所有整数值时,Redis就会使用这些共享对象,而不创建新对象。
示例:
创建一个值为100的键A,使用object refcount 命令查看键A的值对象引用计数为2;再创建一个置为100的键B,此时引用计数为3。
引用这个值对象的分别为:持有这个值对象的服务器程序、键A和B。
注:使用redis4.0测试了一下,对于0-9999,object refcount
命令都返回 2147483647,对于10000,就返回1
redis> set A 100
OK
redis> object refcount A
(integer) 2
redis> set B 100
OK
redis> object refcount A
(integer) 3
redis> object refcount B
(integer) 3
共享值对象的说明如下图:
注:共享对象不仅是字符串使用,其他对象的数据结构中有嵌套了字符串对象都可以使用这些共享对象。
三、对象的空转时长
redisObject还包含一个lru
属性,该属性记录了对象最后一次被命令程序访问的时间:
typedef struct redisObject {
// ...
unsigned lru:22;
// ...
} robj;
object idletime
命令可以打印给定键的空转时长,这个值是通过 当前时间 - 值对象的lru时间
得出的。
示例:
redis> set str test
OK
# 等待一段时间
redis> object idletime str
(integer) 5
# 访问str
redis> get str
"test"
# 键活跃,空转时间清0
redis> object idletime str
(integer) 0
注:object idletime命令可以打印空转时间,还有一个作用:
- 如果服务器打开了maxmemory选项,并且服务器回收内存的算法为 volatile-lru 或者 allkeys-lru,此时当服务器占用的内存数超过了 maxmemory 选项所设置的上限值,空转时间较长的那部分键会优先被服务器释放,从而回收内存。