Python垃圾回收机制
python垃圾回收机制和内存管理机制
引用计数为主, 标记清除和分代回收为辅+缓存机制
1.引用计数器
1.1 环状的双线链表 refchain
在python程序中创建的任何对象都会存在refchain双向链表中
name = 'ds'
age = 18
hobby = ['篮球','美女']
# str
内部会创建一个结构体数据 [上一个对象(指针),下一个对象(指针),类型,引用计数]
# name = 'ds'
new = name new 不会重新创建一个ds的数据, 是指向ds这个数据,引用计数则加+1
# float 多一个val , 存储数据具体值
内部会创建一个结构体数据 [上一个对象(指针),下一个对象(指针),类型,引用计数,val=18]
age = 18
# list , 多存一个items存放的元素容器 和元素个数
内部会创建一个结构体数据 [上一个对象(指针),下一个对象(指针),类型,引用计数,items=元素,元素个数]
hobby = ['篮球','美女']
在C源码中如何体现每个对象中有相同的值 : PyObject结构体(4个值)
有多个元素组成的对象:PyObject结构体(4个值)+ob_size(元素个数)
# C 源码.
PyObject_VAR_HEAD
// 宏定义 , 包含上一个,下一个,用户构建双向链表,(放到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;
1.2 类型封装的结构
-
float 类型
typedef struct { PyObject_HEAD double ob_fval; // 存放 具体数据 } PyFloatObject; // 举例子 data = 3.14 _ob_next : refchain 双向链表中的上一个对象 _ob_prev : refchain 双线链表中的下一个对象 ob_refcnt : 1 // 引用计数 ob_type : float //类型 ob_fval : 3.14
-
int 类型
struct _longobject { PyObject_VAR_HEAD digit ob_digit[1]; };
-
list 类型
typedef struct { PyObject_VAR_HEAD /* Vector of pointers to list elements. list[0] is ob_item[0], etc. */ PyObject **ob_item; // 元素列表 /* ob_item contains space for 'allocated' elements. The number * currently in use is ob_size. * Invariants: * 0 <= ob_size <= allocated * len(list) == ob_size * ob_item == NULL implies ob_size == allocated == 0 * list.sort() temporarily sets allocated to -1 to detect mutations. * * Items must normally not be NULL, except during construction when * the list is not yet visible outside the function that builds it. */ Py_ssize_t allocated; // 元素个数 } PyListObject;
-
tuple 类型
typedef struct { PyObject_VAR_HEAD PyObject *ob_item[1]; /* ob_item contains space for 'ob_size' elements. * Items must normally not be NULL, except during construction when * the tuple is not yet visible outside the function that builds it. */ } PyTupleObject;
-
dict 类型
typedef struct { PyObject_HEAD /* Number of items in the dictionary */ Py_ssize_t ma_used; /* Dictionary version: globally unique, value change each time the dictionary is modified */ uint64_t ma_version_tag; PyDictKeysObject *ma_keys; /* If ma_values is NULL, the table is "combined": keys and values are stored in ma_keys. If ma_values is not NULL, the table is splitted: keys are stored in ma_keys and values are stored in ma_values */ PyObject **ma_values; } PyDictObject;
1.3 引用计数器
v1 = 3.14
v2 = 1
v3 = (1,2,3)
在python程序运行过程中
,会根据数据类型的不同找到其对应的结构体. 根据结构体中的字段来创建相关的数据,然后将对象添加到refchain双线链表中
在C源码中,有两个关键的结构体: PyObject , PyVarObject
每个对象中都有ob_refcnt
, 这就是引用计数器.默认值是1,当其他变量引用对象时,引用计数器就会发生变化
-
引用
a = 999 b = a # 999 对象 的引用计数器就会 +1
-
删除
a = 999 b = a del b # 1. b变量删除, 2. b对应的对象的引用计数器 -1 del a # 1. a变量删除, 2. a对应的对象的引用计数器 -1 , 就是 0 # 当一个对象的引用计数器 为 0 时, 意味着没有再使用这个变量, 这个变量就是垃圾. 垃圾就要进行垃圾回收机制 # 垃圾回收,两件事 : 1. 对象从refchain双线链表中移除 2. 将对象销毁,内存归还.
1.4 循环引用问题
v1 = [1,2,3]
v2 = [4,5,6]
v1.append(v2) // v2 追加到v1列表中, v2的引用计数器+1,最终为2
v2.append(v1) // v1 追加到v2列表中, v1的引用计数器+1,最终为2
del v1 // 删除v1 变量,引用计数器-1
del v2 // 删除v2 变量,引用计数器-1
# 问题:
此时, v1和v2的引用计数器还是1,不是0,所以不会被当做垃圾给回收?
`标记清除` : 解决这样的问题
2.标记清除
目的: 为了解决引用计数器
循环引用的不足
实现:在python的底层,在维护一个链表,链表中专门存放那些可能存在循环引用的对象(list/set/tuple)
在Python内部某种情况下
触发,会扫描可能存在循环引用的链表中
的每一个元素,检查是否有循环引用,如果有则让双方引用计数器 -1 ;如果是0则垃圾回收
3.分代回收
将肯能存在循环引用的对象维护成3个链表
- 0 代 : 0代中对象个数达到700个扫描一次
- 1 代 : 0代扫描10次,则 1代扫描一次
- 2 代 : 1代扫描10次,则 2代扫描一次
4.小结
在python中维护了一个refchain的双向环状链表, 这个链表中存储了程序中创建的所有对象,每种类型的对象中都有一个 ob_refcnt
引用计数器的值,引用个数+1 , -1 , 最后当引用计数器变为0时,会进行垃圾回收(对象销毁,refchain中移除)
但是,在python中对于那些可以有多个元素组成的对象,可能会存在循环引用的问题,为了解决这个问题python又引用了标记清除
和分代回收
,在python内部维护了4个链表:
- refchain
- 2代 10次
- 1代 10次
- 0代 700个对象
在python源码内部,当达到各自的阈值时,就会触发扫描链表进行标记清除的动作(如果有循环引用,则各自-1引用)
但是:源码内部在上述的流程中提出了优化机制缓存
5.python缓存
5.1 小数据池(int , str字符串)
为了避免重复的创建和销毁一些常见对象,维护了一个池.
v1 = 7
v2 = 9
v3 = 9
# python 为了节省资源
1. 在启动解释器的时候, 内部会创建`-5,-4,....256`范围数值
2. 在内部 v1 =7 不会创建新的新对象,也不会开辟内存.直接上池中去取数据
# print(id(v2),id(v3))
3.超过 -5 ... 256 这个范围,重新开辟内存,创建对象
5.2 free_list(float:100/tuple:20/dict:80/list:80)
当引用计数器为0时,按理说是要进行垃圾回收. python内部不会去回收,而是将对象添加到free_list这个链表中作为缓存. 以后再去创建对象时,不会再去开辟内存,直接使用free_list中的数据
v1 = 3.14 # 1. 开辟内存,创建对象,引用计数器为1,并添加到refchain链表中
del v1 # 2. 从refchain中移除 , 将这个对象添加到free_list中,如果free_list链表缓存满了,再进行垃圾回收
v9 = 999.99 # 3. 不会重新开辟内存,直接去free_list中获取一个float类型的数据,对象内部数据初始化,再放到refchain链表中