type
status
date
slug
summary
tags
category
icon
password
Property
内存回收
因为 C 语言并不具备自动的内存回收功能, 所以 Redis 在自己的对象系统中构建了一个引用计数技术实现的内存回收机制, 通过这一机制, 程序可以通过跟踪对象的引用计数信息, 在适当的时候自动释放对象并进行内存回收。
每个对象的引用计数信息由
redisObject
结构的 refcount
属性记录:对象的引用计数信息会随着对象的使用状态而不断变化:
- 在创建一个新对象时, 引用计数的值会被初始化为
1
;
- 当对象被一个新程序使用时, 它的引用计数值会被增一;
- 当对象不再被一个程序使用时, 它的引用计数值会被减一;
- 当对象的引用计数值变为
0
时, 对象所占用的内存会被释放。
修改对象引用计数的 API:
函数 | 作用 |
incrRefCount | 将对象的引用计数值增一 |
decrRefCount | 将对象的引用计数值减一, 当对象的引用计数值等于 0 时, 释放对象 |
resetRefCount | 将对象的引用计数值设置为 0 , 但并不释放对象, 这个函数通常在需要重新设置对象的引用计数值时使用 |
对象的整个生命周期可以划分为创建对象、操作对象、释放对象三个阶段。一个字符串对象从创建到释放的整个过程:
对象共享
除了用于实现引用计数内存回收机制之外, 对象的引用计数属性还带有对象共享的作用。
假设键 A 创建了一个包含整数值
100
的字符串对象作为值对象:如果这时键 B 也要创建一个同样保存了整数值
100
的字符串对象作为值对象, 那么服务器有以下两种做法:- 为键 B 新创建一个包含整数值
100
的字符串对象
- 让键 A 和键 B 共享同一个字符串对象,更节约内存
在
Redis
中, 让多个键共享同一个值对象需要执行以下两个步骤:- 将数据库键的值指针指向一个现有的值对象
- 将被共享的值对象的引用计数增一
包含整数值
100
的字符串对象同时被键 A 和键 B 共享,除了对象的引用计数从之前的1
变成了2
之外, 其他属性都没有变化:共享对象机制对于节约内存非常有帮助, 数据库中保存的相同值对象越多, 对象共享机制就能节约越多的内存。
比如说, 假设数据库中保存了整数值
100
的键不只有键 A 和键 B 两个, 而是有一百个, 那么服务器只需要用一个字符串对象的内存就可以保存原本需要使用一百个字符串对象的内存才能保存的数据。目前来说,
Redis
会在初始化服务器时, 创建一万个字符串对象, 这些对象包含了从 0
到 9999
的所有整数值, 当服务器需要用到值为 0
到 9999
的字符串对象时, 服务器就会使用这些共享对象, 而不是新创建对象。创建共享字符串对象的数量可以通过修改
redis.h/REDIS_SHARED_INTEGERS
常量来修改。如果创建一个值为
100
的键 A
, 并使用 OBJECT REFCOUNT 命令查看键 A
的值对象的引用计数, 发现值对象的引用计数为 2
:引用这个值对象的两个程序分别是持有这个值对象的服务器程序, 以及共享这个值对象的键
A
:如果再创建一个值为
100
的键 B
, 那么键 B
也会指向包含整数值 100
的共享对象, 使得共享对象的引用计数值变为 3
:另外, 这些共享对象不单单只有字符串键可以使用, 那些在数据结构中嵌套了字符串对象的对象(
linkedlist
编码的列表对象、 hashtable
编码的哈希对象、 hashtable
编码的集合对象、以及 zset
编码的有序集合对象)都可以使用这些共享对象。为什么 Redis 不共享包含字符串的对象?
当服务器考虑将一个共享对象设置为键的值对象时, 程序需要先检查给定的共享对象和键想创建的目标对象是否完全相同, 只有在共享对象和目标对象完全相同的情况下, 程序才会将共享对象用作键的值对象, 而一个共享对象保存的值越复杂, 验证共享对象和目标对象是否相同所需的复杂度就会越高, 消耗的 CPU 时间也会越多:
- 如果共享对象是保存整数值的字符串对象, 那么验证操作的复杂度为
- 如果共享对象是保存字符串值的字符串对象, 那么验证操作的复杂度为
- 如果共享对象是包含了多个值(或者对象的)对象, 比如列表对象或者哈希对象, 那么验证操作的复杂度将会是
因此, 尽管共享更复杂的对象可以节约更多的内存, 但受到 CPU 时间的限制,
Redis
只对包含整数值的字符串对象进行共享