引用计数器为主,标记清除和分代回收为辅,+ 缓存机制
refchain
在Python中存在一个 环状双向链表 refchain,其存储着在Python中创建的所有对象
#define PyObject_HEAD PyObject ob_base;
#define PyObject_VAR_HEAD PyVarObject ob_base;
// 宏定义,包含 上⼀个、下⼀个,⽤于构造双向链表⽤。(放到refchain链表中时,要⽤到)
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; \
struct _object *_ob_prev;
typedef struct _object {
_PyObject_HEAD_EXTRA // ⽤于构造双向链表
Py_ssize_t ob_refcnt; // 引⽤计数器
struct _typeobject *ob_type; // 数据类型
} PyObject;
typedef struct {
PyObject ob_base; // PyObject对象
Py_ssize_t ob_size; /* Number of items in variable part,即:元素个数 */
} PyVarObject;
创建对象的两个关键的结构体:PyObject、PyVarObject
- PyObject:包含了4个值,上一个对象
_ob_next
、下一个对象_ob_prev
、引用个数ob_refcnt
、类型ob_type
- PyVarObject:由多个元素组成的对象除了以上4个值还包含 元素的个数
ob_size
引用计算器
在 refchain 中的所有对象内部都有⼀个 ob_refcnt
⽤来保存当前对象的引⽤计数器(即自己被引用的次数)
当引用计数器为0时,(无人使用)则为垃圾需要回收。
回收:
- 对象从 refchain 移除
- 将对象销毁,释放内存
标记清除
v1 = [11,22,33] # refchain中创建⼀个列表对象,由于v1=对象,所以列表引对象⽤计数器为1.
v2 = [44,55,66] # refchain中再创建⼀个列表对象,因v2=对象,所以列表对象引⽤计数器为1.
v1.append(v2) # 把v2追加到v1中,则v2对应的[44,55,66]对象的引⽤计数器加1,最终为2.
v2.append(v1) # 把v1追加到v1中,则v1对应的[11,22,33]对象的引⽤计数器加1,最终为2.
del v1 # 引⽤计数器-1
del v2 # 引⽤计数器-1
由于循环引⽤的问题,他们的引⽤计数器不为0(永远不会被销毁,内存一直被占)
标记清除:用一个链表,专门存放可能存在循环引用的对象(list、tuple、dict、set …)。在某种情况下,检查是否有循环引用,若有则双方的引用计数器-1。
分代回收
将可能存在循环引用的对象,拆分到3个链表(分别称为 0\1\2 代)
当链表达到阈值时,对应的链表进行扫描,除循环引用外,其余引用计数器各自-1
阈值
- 0代:0代对象的数量
- 1代:0代的扫描次数
- 2代:1代的扫描次数
缓存机制
反复的创建和销毁会使程序的执⾏效率变低
python 在启动时会有一些池(数据池)用来存放一些常见的对象(需要时,直接从池中获取)
一个对象的引用计数器为 0 时,内存不会直接回收,而是会被添加到 free_list
中当缓存。
之后再去创建对象时,不再重新开辟内存,而是直接使用 free_list
将原本的对象初始化,再放到 refchain
中
小结
Python 垃圾回收机制综合运用引用计数、标记清除和分代回收策略并辅以缓存机制,既能及时回收无用对象,又能解决循环引用问题,还通过分代和缓存提升效率,保障程序内存管理的高效与稳定。