python的垃圾回收
python的垃圾回收
目录
一、白话垃圾回收
通俗理解什么是内存管理和垃圾回收的过程
1、大管家refchain
在Python的C源码中有⼀个名为refchain的环状双向链表,这个链表⽐较⽜逼了,因为Python程序中⼀旦创建
对象都会把这个对象添加到refchain这个链表中。也就是说他保存着所有的对象。例如:
age = 18
name = ”武沛齐“
2、引用计数器
在refchain中的所有对象内部都有⼀个ob_refcnt⽤来保存当前对象的引⽤计数器,顾名思义就是⾃⼰被引⽤的次数,例如:
age = 18
name = "武沛⻬"
nickname = name
上述代码表示内存中有 18 和 “武沛⻬” 两个值,他们的引⽤计数器分别为:1、2 。
当值被多次引⽤时候,不会在内存中重复创建数据,⽽是引⽤计数器+1 。 当对象被销毁时候同时会让引⽤计数器-1,如果引⽤计数器为0,则将对象从refchain链表中摘除,同时在内存中进⾏销毁(暂不考虑缓存等特殊情况)
age = 18 # 对象 18 的引用计数器为 1
number = age # 对象 18 的引用计数器 +1,现在为 2
del age # 删除变量 age,引用计数器 -1,现在为 1
def run(arg):
print(arg) # 临时对对象 18 增加一个引用计数器
# 函数执行完毕后,引用计数器恢复
run(number) # 刚开始执行函数时,引用计数器 +1,函数结束后引用计数器 -1
num_list = [11, 22, number] # 对象 18 的引用计数器 +1,现在为 2
3、标记清除&分代回收
基于引⽤计数器进⾏垃圾回收⾮常⽅便和简单,但他还是存在循环引⽤的问题,导致⽆法正常的回收⼀些数据,例如:
v1 = [11, 22, 33] # 创建列表对象 [11, 22, 33],引用计数为 1(v1 引用它)
v2 = [44, 55, 66] # 创建列表对象 [44, 55, 66],引用计数为 1(v2 引用它)
v1.append(v2) # 将 v2 对应的列表 [44, 55, 66] 添加到 v1 中,v2 引用计数 +1,变为 2
v2.append(v1) # 将 v1 对应的列表 [11, 22, 33] 添加到 v2 中,v1 引用计数 +1,变为 2
del v1 # 删除变量 v1,对 [11, 22, 33] 的引用计数 -1,但仍然被 v2 引用,引用计数变为 1
del v2 # 删除变量 v2,对 [44, 55, 66] 的引用计数 -1,但仍然被 v1 引用,引用计数变为 1
Python 使用 引用计数 作为垃圾回收的主要方式,同时还有一个 分代垃圾回收器,用来处理循环引用的问题。当循环引用发生时,引用计数无法回收这些对象,但垃圾回收器可以通过标记清除的方式回收它们。
对于上述代码会发现,执⾏del操作之后,没有变量再会去使⽤那两个列表对象,但由于循环引⽤的问题,他们的引⽤计数器不为0,所以他们的状态:永远不会被使⽤、也不会被销毁。项⽬中如果这种代码太多,就会导致内存⼀直被消耗,直到内存被耗尽,程序崩溃。
为了解决循环引⽤的问题,引⼊了标记清除技术,专⻔针对那些可能存在循环引⽤的对象进⾏特殊处理,可能存在循环应⽤的类型有:列表、元组、字典、集合、⾃定义类等那些能进⾏数据嵌套的类型。
标记清除:创建特殊链表专⻔⽤于保存 列表、元组、字典、集合、⾃定义类等对象,之后再去检查这个链表中的对象是否存在循环引⽤,如果存在则让双⽅的引⽤计数器均 – 1 。
分代回收:对标记清除中的链表进⾏优化,将那些可能存在循引⽤的对象拆分到3个链表,链表称为:0/1/2三代,每代都可以存储对象和阈值,当达到阈值时,就会对相应的链表中的每个对象做⼀次扫描,除循环引⽤各⾃减1并且销毁引⽤计数器为0的对象
// 分代的C源码
#define NUM_GENERATIONS 3 表示 Python 的垃圾回收分为 3 代(第 0 代、第 1 代、第 2 代)。
struct gc_generation {
uintptr_t head[2]; // 代链表头节点
int threshold; // 回收阈值
int count; // 回收计数
};
# generations 是一个数组,表示各代的垃圾回收信息。
# 数组中每一项对应一个代:
# 第 0 代:管理新创建的对象。第 1 代:管理从第 0 代幸存下来的对象。第 2 代:管理从第 1 代再次幸存的对象。
struct gc_generation generations[NUM_GENERATIONS] = {
/* PyGC_Head, threshold, count */
{{(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)}, 700, 0}, // 第 0 代
{{(uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)}, 10, 0}, // 第 1 代
{{(uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)}, 10, 0}, // 第 2 代
};
# 第 0 代的阈值为 700,表示当第 0 代的对象达到 700 个时触发垃圾回收。
# 第 1 代和第 2 代的阈值为 10。
特别注意:0代和1、2代的threshold和count表示的意义不同。
·代,count表示0代链表中对象的数量,threshold表示0代链表对象个数阈值,超过则执⾏⼀次0代扫描检查。
·1代,count表示0代链表扫描的次数,threshold表示0代链表扫描的次数阈值,超过则执⾏⼀次1代扫描检查。
·2代,count表示1代链表扫描的次数,threshold表示1代链表扫描的次数阈值,超过则执⾏⼀2代扫描检查。
分代回收的优点在于:将垃圾回收的频率集中在短命的对象上,从而提高了性能。
4、情景模拟
根据C语⾔底层并结合图来讲解内存管理和垃圾回收的详细过程。
第⼀步:当创建对象age=19
时,会将对象添加到refchain链表中
第⼆步:当创建对象num_list = [11,22]
时,会将列表对象添加到 refchain 和 generations 0代中。
第三步:新创建对象使generations的0代链表上的对象数量⼤于阈值700时,要对链表上的对象进⾏扫描检查。
当0代⼤于阈值后,底层不是直接扫描0代,⽽是先判断2、1是否也超过了阈值。
·如果2、1代未达到阈值,则扫描0代,并让1代的 count + 1 。
·如果2代已达到阈值,则将2、1、0三个链表拼接起来进⾏全扫描,并将2、1、0代的count重置为0.
·如果1代已达到阈值,则讲1、0两个链表拼接起来进⾏扫描,并将所有1、0代的count重置为0.
对拼接起来的链表在进⾏扫描时,主要就是剔除循环引⽤和销毁垃圾,详细过程为:
·扫描链表,把每个对象的引⽤计数器拷⻉⼀份并保存到 gc_ref
s中,保护原引⽤计数器。
·再次扫描链表中的每个对象,并检查是否存在循环引⽤,如果存在则让各⾃的gc_refs
减 1 。
·再次扫描链表,将 gc_refs
为 0 的对象移动到unreachable
链表中;不为0的对象直接升级到下⼀代链表中。
·处理unreachable
链表中的对象的 析构函数 和 弱引⽤,不能被销毁的对象升级到下⼀代链表,能销毁的保留在此链表
析构函数,指的就是那些定义了__del__⽅法的对象,需要执⾏之后再进⾏销毁处理。
·最后将 unreachable
中的每个对象销毁并在refchain链表中移除(不考虑缓存机制)。⾄此,垃圾回收的过程结束。
5、缓存机制
从上⽂⼤家可以了解到当对象的引⽤计数器为0时,就会被销毁并释放内存。⽽实际上他不是这么的简单粗暴,因为反复的创建和销毁会使程序的执⾏效率变低。Python中引⼊了“缓存机制”机制。
例如:引⽤计数器为0时,不会真正销毁对象,⽽是将他放到⼀个名为 free_list 的链表中,之后会再创建对象时不会在重新开辟内存,⽽是在free_list中将之前的对象来并重置内部的值来使⽤。
·float
类型,维护的free_list
链表最多可缓存100个float
对象。
# 创建一个浮点数对象,并将其添加到refchain链表
v1 = 3.14 # 为float对象开辟内存,将其引用添加到refchain链表
print(id(v1)) # 打印对象的内存地址,例如:4436033488
# 删除v1变量的引用
del v1 # 引用计数器 -1,如果引用计数器为0,移除refchain链表。
# 对象未销毁,而是被添加到float类型的free_list缓存池中。
# 创建另一个浮点数对象
v2 = 9.999 # 优先尝试从float类型的free_list中获取对象。
# 如果free_list中有缓存对象,则重用并重置为9.999;
# 如果free_list为空,则重新开辟内存。
print(id(v2)) # 打印对象的内存地址,例如:4436033488
# 注意:
# 当对象的引用计数器为0时,系统会检查float类型的free_list:
# - 如果free_list未满,则将对象缓存到free_list。
# - 如果free_list已满,则直接销毁对象并释放内存。
·int
类型,不是基于free_list
,⽽是维护⼀个small_ints
链表保存常⻅数据(⼩数据池),⼩数据池范围:-5 <= value < 257
。即:重复使⽤这个范围的整数时,不会重新开辟内存。
# 从小数据池 small_ints 中获取整数 38 对象
v1 = 38 # small_ints 池中已经存在值为 38 的整数对象。
# 将 small_ints 中的该对象添加到 refchain,并将其引用计数器 +1。
print(id(v1)) # 打印对象的内存地址,例如:4514343712
# 再次从 small_ints 中获取整数 38 对象
v2 = 38 # small_ints 中已经存在 38 的整数对象,
# 因此不会重新创建对象,而是复用该对象,将引用计数器 +1。
print(id(v2)) # 打印对象的内存地址,和 v1 的地址相同,例如:4514343712
# 注意:
# 1. 在 Python 解释器启动时,整数范围 -5 至 256 的对象会被提前加载到 small_ints 池中,
# 并将其引用计数器初始化为 1。
# 2. 当代码中使用 -5 至 256 范围内的整数时,会直接从 small_ints 池中获取,
# 并将引用计数器 +1。
# 3. small_ints 中的整数对象的引用计数器永远不会归零,因为它们在解释器生命周期内始终可用,
# 所以不会被销毁。
·str
类型,维护unicode_latin1[256]
链表,内部将所有的ascii
字符缓存起来,以后使⽤时就不再反复创建。
# 示例 1:单字符字符串驻留
v1 = "A" # 创建字符串 "A",将其添加到字符串驻留池中
print(id(v1)) # 输出:4517720496(示例地址,仅供参考)
del v1 # 删除 v1 变量,对象引用计数器减 1,但驻留的字符串 "A" 仍在驻留池中
v2 = "A" # 再次创建 "A",Python 会直接复用驻留池中的字符串对象
print(id(v2)) # 输出:4517720496(与 v1 相同的地址)
# 注:
# Python 对含字母、数字、下划线的短字符串使用驻留机制,
# 这些字符串在首次创建时会存入驻留池,后续会直接复用已有对象。
# 示例 2:驻留机制的字符串复用
v1 = "wupeiqi" # 创建字符串 "wupeiqi",存入驻留池
v2 = "wupeiqi" # Python 检测到驻留池中已存在相同字符串,直接复用对象
print(id(v1) == id(v2)) # 输出:True,说明两个变量引用同一对象
# 驻留机制特点:
# 1. 针对只含字母、数字、下划线的短字符串。
# 2. 驻留的字符串不需要像 free_list 那样始终驻留内存,
# 但只要在内存中有相同的字符串,就可以被复用。
·list
类型,维护的free_list
数组最多可缓存80个list
对象。
# 示例 1:创建和删除列表对象
v1 = [11, 22, 33] # 创建一个列表对象,内存分配给该对象
print(id(v1)) # 输出 v1 的内存地址(示例地址,仅供参考)
del v1 # 删除变量 v1,列表对象的引用计数器减 1。如果引用计数器变为 0,内存可能被回收。
# 示例 2:创建新的列表对象
v2 = ["武", "沛齐"] # 创建另一个列表对象
print(id(v2)) # 输出 v2 的内存地址,可能与 v1 的地址相同
# 注意:
# - Python 的内存管理依赖引用计数,同时结合垃圾回收机制。
# - 当一个对象的引用计数器归零时,其占用的内存可能会被回收。
# - 新创建的对象可能会复用刚释放的内存地址(具体是否复用取决于垃圾回收器的行为)。
·tuple
类型,维护⼀个free_list
数组且数组容量20,数组中元素可以是链表且每个链表最多可以容纳2000个元组对象。元组的free_list
数组在存储数据时,是按照元组可以容纳的个数为索引找到free_list
数组中对应的链表,并添加到链表中。
# 示例:元组对象的缓存与复用
v1 = (1, 2) # 创建一个元组对象,元组的大小为 2
print(id(v1)) # 输出 v1 的内存地址
del v1 # 删除变量 v1,元组对象引用计数减 1
# 如果元组大小小于等于 20,Python 会将其缓存到 `free_list` 对应的链表中,而不是立即销毁。
v2 = ("武沛齐", "Alex") # 创建一个新的元组对象,大小也是 2
print(id(v2)) # 输出 v2 的内存地址,可能与 v1 相同(因为内存可能被复用)
# 注意:具体行为可能因 Python 版本而异,尤其是对象缓存与内存复用的实现细节。
·dict
类型,维护的free_list
数组最多可缓存80个dict
对象。
# 示例:字典对象的内存复用
v1 = {"k1": 123} # 创建一个字典对象
print(id(v1)) # 输出 v1 的内存地址
del v1 # 删除 v1,字典对象的引用计数减 1
# 字典对象通常不会被缓存,但内存可能被释放并复用。
v2 = {"name": "武沛齐", "age": 18, "gender": "男"} # 创建一个新的字典对象
print(id(v2)) # 输出 v2 的内存地址,可能与 v1 相同(如果内存地址被复用)
# 注意:字典对象的内存复用不是 Python 明确保证的行为,而是可能由内存分配机制决定。
二、C语⾔源码分析
1、两个重要的结构体
// 定义 PyObject 头部结构
#define PyObject_HEAD PyObject ob_base;
#define PyObject_VAR_HEAD PyVarObject ob_base;
// 宏定义:构造双向链表所需的字段,表示上一个和下一个对象指针
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; /* 指向链表中的下一个对象 */ \
struct _object *_ob_prev; /* 指向链表中的上一个对象 */
// 定义 PyObject 结构体,用于表示所有 Python 对象的基础结构
typedef struct _object {
_PyObject_HEAD_EXTRA // 双向链表指针
Py_ssize_t ob_refcnt; // 引用计数器
struct _typeobject *ob_type; // 指向该对象的类型信息
} PyObject;
// 定义 PyVarObject 结构体,用于表示可变大小的 Python 对象
typedef struct {
PyObject ob_base; // 继承 PyObject 的基础结构
Py_ssize_t ob_size; // 变量部分的元素数量
} PyVarObject;
这两个结构体PyObject和PyVarObject是基⽯,他们保存这其他数据类型公共部分,例如:每个类型的对象在创建时都有PyObject中的那4部分数据;list/set/tuple等由多个元素组成对象创建时都有PyVarObject中的那5部分数据。
宏定义 PyObject_HEAD
和 PyObject_VAR_HEAD
:
PyObject_HEAD
定义了所有 Python 对象的基础部分,包括引用计数和类型信息。PyObject_VAR_HEAD
在 PyObject
的基础上添加了 ob_size
字段,用于记录可变大小对象的元素数量。双向链表支持:
_PyObject_HEAD_EXTRA
宏定义了两个指针 _ob_next
和 _ob_prev
,用于构建引用链表(refchain
),方便管理 Python 对象。基础对象结构 (PyObject
):
ob_refcnt
是引用计数器,表示该对象当前被引用的次数。ob_type
指向对象的类型信息,标明对象的具体类型。可变对象结构 (PyVarObject
):
PyObject
的基础上增加了 ob_size
,专用于描述包含多个元素的对象(如列表、元组、字符串等)。2、常⻅类型结构体
平时我们在创建⼀个对象时,本质上就是实例化⼀个相关类型的结构体,在内部保存值和引⽤计数器等。
·float
类型
typedef struct {
PyObject_HEAD // 包含对象的基础信息,如引用计数和类型信息
double ob_fval; // 用于存储浮点数的具体值
} PyFloatObject;
·int
类型
struct _longobject {
PyObject_VAR_HEAD // 用于支持变长对象的宏
digit ob_digit[1]; // 存储整数的数组,支持任意精度
};
/* Long (arbitrary precision) integer object interface */
typedef struct _longobject PyLongObject; // 定义长整型对象类型
·str
类型
typedef struct {
PyObject_HEAD
Py_ssize_t length; /* 字符串中的字符数 */
Py_hash_t hash; /* 哈希值;未设置时为 -1 */
struct {
unsigned int interned:2;
/* 字符大小:
- PyUnicode_WCHAR_KIND (0):
* 字符类型 = wchar_t (16 或 32 位,取决于平台)
- PyUnicode_1BYTE_KIND (1):
* 字符类型 = Py_UCS1 (8 位,无符号)
* 所有字符的范围在 U+0000 到 U+00FF 之间 (latin1)
* 如果设置了 ascii,所有字符的范围在 U+0000 到 U+007F 之间 (ASCII),否则至少有一个字符在 U+0080 到 U+00FF 之间
- PyUnicode_2BYTE_KIND (2):
* 字符类型 = Py_UCS2 (16 位,无符号)
* 所有字符的范围在 U+0000 到 U+FFFF 之间 (BMP)
* 至少有一个字符在 U+0100 到 U+FFFF 之间
- PyUnicode_4BYTE_KIND (4):
* 字符类型 = Py_UCS4 (32 位,无符号)
* 所有字符的范围在 U+0000 到 U+10FFFF 之间
* 至少有一个字符在 U+10000 到 U+10FFFF 之间
*/
unsigned int kind:3;
unsigned int compact:1;
unsigned int ascii:1;
unsigned int ready:1;
unsigned int :24;
} state;
wchar_t *wstr; /* wchar_t 表示的字符串(以 null 终止) */
} PyASCIIObject;
typedef struct {
PyASCIIObject _base;
Py_ssize_t utf8_length; /* utf8 编码的字节数,不包括终止的 \0 */
char *utf8; /* UTF-8 编码表示(以 null 终止) */
Py_ssize_t wstr_length; /* wstr 中的字符数,可能有替代字符算作两个字符 */
} PyCompactUnicodeObject;
typedef struct {
PyCompactUnicodeObject _base;
union {
void *any;
Py_UCS1 *latin1;
Py_UCS2 *ucs2;
Py_UCS4 *ucs4;
} data; /* 标准化、最小形式的 Unicode 缓冲区 */
} PyUnicodeObject;
·list
类型
typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item; /* 列表中的元素指针数组 */
Py_ssize_t allocated; /* 已分配的空间大小 */
} PyListObject;
·tuple
类型
typedef struct {
PyObject_VAR_HEAD
PyObject *ob_item[1]; /* 元组中的元素指针数组 */
} PyTupleObject;
·dict
类型
typedef struct {
PyObject_HEAD
Py_ssize_t ma_used; /* 字典中使用的键值对数量 */
PyDictKeysObject *ma_keys; /* 指向字典键对象的指针 */
PyObject **ma_values; /* 指向字典值的指针数组 */
} PyDictObject;
通过常⻅结构体可以基本了解到本质上每个对象内部会存储的数据。
为python字符串在处理时需要考虑到编码的问题,在内部规定(⻅源码结构体):
·字符串只包含ascii,则每个字符⽤1个字节表示,即:latin1
·字符串包含中⽂等,则每个字符⽤2个字节表示,即:ucs2
·字符串包含emoji等,则每个字符⽤4个字节表示,即:ucs4
3、Float类型
创建
val = 3.14
类似于这样创建⼀个float对象时,会执⾏C源码中的如下代码:
// Objects/floatobject.c
// 用于缓存 float 对象的链表
static PyFloatObject *free_list = NULL; // 空闲 float 对象链表
static int numfree = 0; // 当前空闲对象的数量
PyObject *
PyFloat_FromDouble(double fval)
{
// 如果 free_list 中有可用对象,则从 free_list 链表中取出一个;否则为对象重新分配内存。
PyFloatObject *op = free_list;
if (op != NULL) {
free_list = (PyFloatObject *) Py_TYPE(op); // 更新 free_list 指向下一个空闲对象
numfree--; // 空闲对象数量减一
} else {
// 根据 float 类型的大小,为 float 对象新分配内存。
op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
if (!op)
return PyErr_NoMemory(); // 如果内存分配失败,返回内存不足错误
}
// 对 float 对象进行初始化,例如:引用计数器初始化为 1、添加到 refchain 链表等。
/* 内联执行 PyObject_New */
(void)PyObject_INIT(op, &PyFloat_Type);
// 对 float 对象赋值,即:op->ob_fval = fval
op->ob_fval = fval;
return (PyObject *) op; // 返回创建的 float 对象
}
// Include/objimpl.h
// 初始化 PyObject 的宏定义
#define PyObject_INIT(op, typeobj) \
( Py_TYPE(op) = (typeobj), _Py_NewReference((PyObject *)(op)), (op) )
// Objects/object.c
// 维护了所有对象的一个环状双向链表
static PyObject refchain = {&refchain, &refchain}; // 初始化 refchain 链表
void
_Py_AddToAllObjects(PyObject *op, int force)
{
// 如果 force 为真或对象未在链表中(op->_ob_prev 为 NULL),将对象添加到 refchain 链表中
if (force || op->_ob_prev == NULL) {
op->_ob_next = refchain._ob_next; // 设置新对象的下一个节点为 refchain 的下一个节点
op->_ob_prev = &refchain; // 设置新对象的前一个节点为 refchain
refchain._ob_next->_ob_prev = op; // 更新 refchain 原下一个节点的前向指针为新对象
refchain._ob_next = op; // 更新 refchain 的下一个节点为新对象
}
}
void
_Py_NewReference(PyObject *op)
{
_Py_INC_REFTOTAL; // 增加全局引用计数总数
op->ob_refcnt = 1; // 引用计数器初始化为 1
_Py_AddToAllObjects(op, 1); // 将对象添加到双向链表 refchain 中
_Py_INC_TPALLOCS(op); // 增加类型分配统计计数
}
引用
val = 3.14
data = val
在项⽬中如果出现这种引⽤关系时,会将原对象的引⽤计数器+1。
C源码执⾏流程如下
// Include/object.h
static inline void _Py_INCREF(PyObject *op)
{
_Py_INC_REFTOTAL; // 增加全局引用计数总数
// 对象的引用计数器 +1
op->ob_refcnt++;
}
// 将 Py_INCREF 宏定义为 _Py_INCREF 的封装,适配 _PyObject_CAST 的类型转换
#define Py_INCREF(op) _Py_INCREF(_PyObject_CAST(op))
销毁
val = 3.14
del val
在项⽬中如果出现这种删除的语句,则内部会将引⽤计数器-1,如果引⽤计数器减为0,则进⾏缓存或垃圾回收。
C源码执⾏流程如下:
// Include/object.h
static inline void _Py_DECREF(const char *filename, int lineno, PyObject *op)
{
(void)filename; // 可能未使用,关闭 -Wunused-parameter 警告
(void)lineno; // 可能未使用,关闭 -Wunused-parameter 警告
_Py_DEC_REFTOTAL; // 减少全局引用计数总数
// 引用计数器 -1,如果引用计数器为 0,则执行 _Py_Dealloc 进行缓存回收或垃圾回收
if (--op->ob_refcnt != 0) {
#ifdef Py_REF_DEBUG
if (op->ob_refcnt < 0) {
_Py_NegativeRefcount(filename, lineno, op); // 如果引用计数小于 0,记录负引用计数错误
}
#endif
} else {
_Py_Dealloc(op); // 引用计数为 0,释放对象
}
}
// 将 Py_DECREF 宏定义为 _Py_DECREF 的封装,自动记录调用文件和行号,并适配类型转换
#define Py_DECREF(op) _Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
// Objects/object.c
void
_Py_Dealloc(PyObject *op)
{
// 找到对象类型的 tp_dealloc 函数,用于对象的回收处理
destructor dealloc = Py_TYPE(op)->tp_dealloc;
// 在 refchain 双向链表中移除此对象
_Py_ForgetReference(op);
// 调用 tp_dealloc 函数执行具体的缓存或垃圾回收操作
(*dealloc)(op);
}
void
_Py_ForgetReference(PyObject *op)
{
...
// 在 refchain 双向链表中移除此对象
op->_ob_next->_ob_prev = op->_ob_prev; // 更新后一个节点的前向指针
op->_ob_prev->_ob_next = op->_ob_next; // 更新前一个节点的后向指针
op->_ob_next = op->_ob_prev = NULL; // 将当前节点的指针清空
_Py_INC_TPFREES(op); // 增加类型释放统计计数
}
// Objects/floatobject.c
// 定义 float 类型对象缓存的最大数量
#define PyFloat_MAXFREELIST 100
// 当前缓存中的 float 对象数量
static int numfree = 0;
// float 对象的缓存链表
static PyFloatObject *free_list = NULL;
// 定义 float 类型对象的函数对应关系
PyTypeObject PyFloat_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"float", // 类型名称
sizeof(PyFloatObject), // 对象的大小
0,
// tp_dealloc 表示执行 float_dealloc 方法
(destructor)float_dealloc, /* tp_dealloc */
0, /* tp_print */
...
};
// 定义 float 类型对象的释放函数
static void
float_dealloc(PyFloatObject *op)
{
// 检测对象是否是 float 类型
if (PyFloat_CheckExact(op)) {
// 检测缓存链表是否已满,如果已满,则直接销毁对象
if (numfree >= PyFloat_MAXFREELIST) {
// 销毁对象
PyObject_FREE(op);
return;
}
// 将对象加入缓存链表 free_list
numfree++;
Py_TYPE(op) = (struct _typeobject *)free_list; // 将 free_list 的头赋值为对象类型
free_list = op; // 将对象加入链表头
} else {
// 如果对象不是 float 类型,则调用其类型的 tp_free 方法释放内存
Py_TYPE(op)->tp_free((PyObject *)op);
}
}
4、int类型
创建
age = 19
当在python中创建⼀个整型数据时,底层会触发他的如下源码
PyObject *
PyLong_FromLong(long ival)
{
PyLongObject *v;
...
// 优先检查是否在小整数池中,如果在范围内则直接获取,而无需重新开辟内存。(-5 <= value < 257)
CHECK_SMALL_INT(ival);
...
// 如果值不在小整数池范围内,则重新开辟内存并初始化
v = _PyLong_New(ndigits);
if (v != NULL) {
digit *p = v->ob_digit;
Py_SIZE(v) = ndigits * sign;
t = abs_ival;
...
}
return (PyObject *)v;
}
#define NSMALLNEGINTS 5 // 小整数池中存储的负整数数量
#define NSMALLPOSINTS 257 // 小整数池中存储的正整数数量
// 检查是否在小整数池范围内的宏定义
#define CHECK_SMALL_INT(ival) \
do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
return get_small_int((sdigit)ival); \
} while(0)
// 获取小整数池中的对象
static PyObject *
get_small_int(sdigit ival)
{
PyObject *v;
// 从小整数池中获取对应的整数对象
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
// 引用计数器 +1,确保对象不会被回收
Py_INCREF(v);
...
return v;
}
// 创建一个新的长整型对象
PyLongObject *
_PyLong_New(Py_ssize_t size)
{
// 创建 PyLongObject 的指针变量
PyLongObject *result;
...
// 根据数字长度分配内存
result = PyObject_MALLOC(offsetof(PyLongObject, ob_digit) +
size * sizeof(digit));
...
// 初始化内存中的数据,并将对象添加到 refchain 链表中
return (PyLongObject*)PyObject_INIT_VAR(result, &PyLong_Type, size);
}
// 定义一个宏,用于创建可变大小的对象
#define PyObject_NewVar(type, typeobj, n) \
( (type *) _PyObject_NewVar((typeobj), (n)) )
// 初始化一个可变大小对象(PyVarObject)
static inline PyVarObject*
_PyObject_INIT_VAR(PyVarObject *op, PyTypeObject *typeobj, Py_ssize_t size)
{
assert(op != NULL); // 确保对象指针不为空
Py_SIZE(op) = size; // 设置对象的大小(数组大小或动态部分的大小)
// 调用 PyObject_INIT 进行基础对象初始化
PyObject_INIT((PyObject *)op, typeobj);
return op;
}
// 定义一个宏,用于基础对象初始化
#define PyObject_INIT(op, typeobj) \
_PyObject_INIT(_PyObject_CAST(op), (typeobj))
// 内联函数,用于基础对象的初始化
static inline PyObject*
_PyObject_INIT(PyObject *op, PyTypeObject *typeobj)
{
assert(op != NULL); // 确保对象指针不为空
Py_TYPE(op) = typeobj; // 设置对象的类型为指定的类型对象
// 检查类型对象是否是堆分配类型
if (PyType_GetFlags(typeobj) & Py_TPFLAGS_HEAPTYPE) {
Py_INCREF(typeobj); // 如果是堆分配类型,增加类型对象的引用计数
}
// 完成对象初始化,并将其加入全局对象链表(refchain)
_Py_NewReference(op);
return op;
}
// 全局变量 refchain:一个环状双向链表,用于跟踪所有 Python 对象
static PyObject refchain = {&refchain, &refchain};
// 将对象添加到全局对象链表 refchain 中
void _Py_AddToAllObjects(PyObject *op, int force)
{
// 如果需要强制添加或对象未在链表中(op->_ob_prev == NULL)
if (force || op->_ob_prev == NULL) {
op->_ob_next = refchain._ob_next; // 新对象的下一个指向 refchain 的下一个
op->_ob_prev = &refchain; // 新对象的上一个指向 refchain
refchain._ob_next->_ob_prev = op; // 更新原链表第一个节点的上一个为新对象
refchain._ob_next = op; // 更新 refchain 的下一个为新对象
}
}
// 初始化新对象的引用计数,并将其加入 refchain 链表
void _Py_NewReference(PyObject *op)
{
_Py_INC_REFTOTAL; // 增加全局引用计数统计值
op->ob_refcnt = 1; // 初始化对象的引用计数为 1
_Py_AddToAllObjects(op, 1); // 将对象添加到 refchain 链表中
_Py_INC_TPALLOCS(op); // 增加分配对象的统计值
}
引用
value = 69
data = value
类似于出现这种引⽤关系时,内部其实就是将对象的引⽤计数器+1,源码同float类型引⽤。
销毁
value = 699
del value
在项⽬中如果出现这种删除的语句,则内部会将引⽤计数器-1,如果引⽤计数器减为0,则直接进⾏垃圾回收。
(int类型是基于⼩数据池⽽不是free_list做的缓存,所以不会在销毁时缓存数据)。
C源码执⾏流程如下:
// Include/object.h
// 减少对象引用计数的函数
static inline void _Py_DECREF(const char *filename, int lineno, PyObject *op)
{
// 以下两行是为了防止未使用的参数触发编译警告
(void)filename;
(void)lineno;
_Py_DEC_REFTOTAL; // 全局引用计数统计值减少 1
// 减少对象的引用计数
if (--op->ob_refcnt != 0) {
#ifdef Py_REF_DEBUG
// 如果引用计数变为负值,触发调试警告
if (op->ob_refcnt < 0) {
_Py_NegativeRefcount(filename, lineno, op);
}
#endif
} else {
// 如果引用计数为 0,则执行对象回收
_Py_Dealloc(op);
}
}
// 宏定义:简化 _Py_DECREF 的调用,同时记录文件名和行号
#define Py_DECREF(op) _Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
// Objects/object.c
void
_Py_Dealloc(PyObject *op)
{
// 找到int类型的 tp_dealloc 函数(int类中没有定义tp_dealloc函数,需要去⽗级PyBaseObject_Type中找tp_dealloc函数)
// 此处体现所有的类型都继承object
destructor dealloc = Py_TYPE(op)->tp_dealloc;
// 在refchain双向链表中摘除此对象。
_Py_ForgetReference(op);
// 执⾏int类型的 tp_dealloc 函数,去进⾏垃圾回收。
(*dealloc)(op);
}
void
_Py_ForgetReference(PyObject *op)
{
...
// 在refchain链表中移除此对象
op->_ob_next->_ob_prev = op->_ob_prev;
op->_ob_prev->_ob_next = op->_ob_next;
op->_ob_next = op->_ob_prev = NULL;
_Py_INC_TPFREES(op);
}
// Objects/longobjet.c
PyTypeObject PyLong_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"int", /* tp_name */
offsetof(PyLongObject, ob_digit), /* tp_basicsize */
sizeof(digit), /* tp_itemsize */
0, /* tp_dealloc */
...
PyObject_Del, /* tp_free */
};
// Objects/typeobject.c
PyTypeObject PyBaseObject_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"object", /* tp_name */
sizeof(PyObject), /* tp_basicsize */
0, /* tp_itemsize */
object_dealloc, /* tp_dealloc */
...
PyObject_Del, /* tp_free */
};
static void
object_dealloc(PyObject *self)
{
// 调用int类型的 tp_free,即:PyObject_Del去销毁对象。
Py_TYPE(self)->tp_free(self);
}
其余str,tuple,dict,list等不再多说
作者:Long韵韵