垃圾回收与内存管理
Python内存管理与垃圾回收
Python垃圾回收
视频链接:https://www.bilibili.com/video/BV1F54114761
参考博客:https://pythonav.com/wiki/detail/6/88/
1. 引用计数器
1.1 环状的双向链表
在python程序中创建的任何对象都会放在refchain链表中。
name="数轴级"
age=18
hobby=["唱","篮球","rap"]
//这些创建的对象都会被放在环形双向链表中
内部会创建一些数据【上一个对象,下一个对象,类型,引用个数】
因为存储在环形链表中,需要指向上一个对象,下一个对象。每一个被创建的对象都有这四个值。
name="数轴级"
new =name //两个对象此时会指向同一个内存 此时的引用个数为2
对于不同的数据类型也会创建不同的数据
内部会创建一些数据【上一个对象,下一个对象,类型,引用个数,val=18】
age=18
【上一个对象,下一个对象,类型,引用个数,item=hobby=["唱","篮球","rap"],元素的个数】
hobby=["唱","篮球","rap"]
refchain底层 C语言源码
9-13行存储了四个固定的数据,【上一个对象,下一个对象,类型,引用个数】使用Pyobject(4个值)结构体体现。
15-18行 对于一个对象有多个值的时候会存储【Pyobject(4个值),元素的个数】使用pyVarObject结构体。
对于
1.2 类型封装的结构体
data=3.14
//内部会创建四个固定值,Pyobject(4个值)
ob_next=refchain中上一个对象
ob_pre=refchain中下一个对象
ob_refcnt=1 引用个数
ob_type= float 数据类型
ob_fval=3.14 值
1.3 引用计数器
v1=2
v2=3.14
v3="自凤凰城"
v4=[1,2,3]
-
当python程序运行时,会根据创建类型的不同找到相应的结构体,根据结构体中的字段进行创建不同的相关数据,然后将对象添加到refchain双向链表中。
-
在C语言源码有2 个重要的结构体, Pyobject【用于创建四个公共的值上一个对象,下一个对象,类型,引用个数 】,对于有多个对象的值使用PyVarObject【Pyobject,元素个数】
-
每个对象中都会有个ob_refcnt就是引用计数器,默认值为1,当有其他变量引用对象是,引用计数器就会发生变化。
-
引用
a=999 b=a #此时a的引用计数器会+1
-
-
删除引用
a=999 b=a del b #b变量删除此时b对应的应用计数器会-1 del a #a变量删除此时a对应的应用计数器会-1
-
当一个对象的引用计数器为0时,意味着没人在使用这个对象了,这个对象就是垃圾,需要进行垃圾回收。
-
回收 1、对象从rechain链表中移除 2、将对象销毁内存归还
引用图示
删除图示
将对象从双向环形链表中删除
1.4 循环引用问题
循环引用与交叉感染
当引用计数器为0时,对象就要进行垃圾回收,但也存在着bug.
第三行代码,v1中使用了v2,导致v2的引用计数器+1,最终为2。
第四行代码,v2中使用了v1,导致v1的引用计数器+1,最终为2。
v1=[11,22,33] #refchain中创建一 个列表对象, 由于v1=对 象,所以列表引对象用计数器为1.
v2=[44,55,66] #refchain中创建一 个列表对象, 由于v1=对 象,所以列表引对象用计数器为1.
v1.appenad(v2) #把v2追加到v1中,则v2对 应的[44, 55 , 66]对象的引用计数器加1,最终为2.
v2.appenad(v1) #把v1追加到v1中,则v1对应的[11, 22,33]对象的引用计数器加1,最终为2.
此时对象的refchain为下图
当进行删除对象时,由于上面都进行了引用引用计数器都为2
del v1 #此时v1的引用计数器为1
del v2 #此时v2的引用计数器为1
由于我们进行了删除v1 2对象操作 但是此时的引用计数器都不为0,并不会被当做垃圾进行回收。此时程序但此时并不会去使用v1 v2 导致占用内存,如果程序中存在大量的这种代码,会有爆栈危险。为了解决循环引用的问题python引入了标记清除。
2. 标记清除
目的:为了解决引用计数器循环引用的不足。
实现:在python的底层在维护一个链表,链表中专门寸法那些可能存在循环引用的对象【列表/元祖/字典/集合】
在python内部某种情况下触发,回去扫描,可能存在循环引用的链表的每个元素,也会扫描每个元素的子元素,检查是否有循环引用,如果有则让双方的引用计数器-1,如果为0时则进行垃圾回收。
但是标记清除也会存在一定的问题:
-
什么时候扫描?
-
可能存在循环引用的链表扫描代价太大,每次扫描耗时太久。
为了解决上述问题python底层实现了分代回收机制。
3 分代回收
将可能存在循环应用的对象维护成3个链表,分成0代 、1代、2代。
什么时候扫描?
- 0代:0代中的对象达到了700个时扫描一次
- 1代:0代扫描10次,1代扫描一次。
- 2代:1代扫描10次,2代扫描一次。
最开始的对象都在0代中添加,直到对象个数到达700个,进行扫描一次,如果是循环引用,进行垃圾回收,如果打不是循环引用,则进行升级将0代中的对象放入到1代中,此时0代的数据为空。1代也会标记0代的数据扫描了一次。继续循环当0代的数据扫描了10次,1代才会扫描一次,同理,当1代扫描10次时才会进行2代扫描。
进行扫描时,0代的阈值是对象的个数,而1代与2代的阈值是扫描的次数。
进行分代回收,很好解决了什么进行扫描,扫描的代价太大的问题。
4. 垃圾回收机制小结
在python中维护了一个refchain的双向链表,这个链表中存储着程序创建的所有对象,每种类型的对象都有一个ob_refcnt的引用计数器的值,当进行引用时计数器的个数+1,删除对象时引用计数器个数-1,最后大纲计数器的个数为0时进行垃圾回收【对象销毁(从内存中移除),双向循环链表中移除对象】。
但是,python中对于那些有对个元素的组成的对象可能会存在循环引用问题,为了解决这个问题python又引入了标记清除和分代回收,在其内部分为了4个链表。
-
rafchain
-
2 代 1代扫描10次
-
1代 0代扫描10次
-
0代 ,对象个数达到700个
在源码内部当达到各自的阈值时,就会触发扫描链表进行标记清除的动作(有循环引用引用计数器各自减1)。
but,源码内部在上述的流程中提出了优化机制。
5 python缓存
5.1 池(int、字符串)
为了避免重复创建和销毁一些常见对象、维护池 。
- int类型,不是基于free_list,而是维护一个small_ints链表保存常见数据(小数据池),小数据池范围:
-5 <= value < 257
。即:重复使用这个范围的整数时,不会重新开辟内存。
#启动解释器时,python内部会帮我们创建[-5,257]的所有对象,提前在内存块上开辟空间。
#我们创建这个对象时,内部不会再次开辟空间,而是直接去池中获取。
v1=7 #内存不会开辟空间,直接进入池中获取。
v2=9
v3=9
v1 = 38 # 去小数据池small_ints中获取38整数对象,将对象添加到refchain并让引用计数器+1。
print( id(v1)) #内存地址:4514343712
v2 = 38 # 去小数据池small_ints中获取38整数对象,将refchain中的对象的引用计数器+1。
print( id(v2) ) #内存地址:4514343712
# 注意:在解释器启动时候-5~256就已经被加入到small_ints链表中且引用计数器初始化为1,代码中使用的值时直接去small_ints中拿来用并将引用计数器+1即可。另外,small_ints中的数据引用计数器永远不会为0(初始化时就设置为1了),所以也不会被销毁。
-
str类型,维护
unicode_latin1[256]
(字符池)链表,内部将所有的ascii字符
缓存起来,以后使用时就不再反复创建。v1 = "A" print( id(v1) ) # 输出:4517720496 del v1 v2 = "A" print( id(v1) ) # 输出:4517720496 # 除此之外,Python内部还对字符串做了驻留机制,针对那么只含有字母、数字、下划线的字符串(见源码Objects/codeobject.c),如果内存中已存在则不会重新在创建而是使用原来的地址里(不会像free_list那样一直在内存存活,只有内存中有才能被重复利用)。 v1 = "wupeiqi" v2 = "wupeiqi" print(id(v1) == id(v2)) # 输出:True
5.2 free_list
对象的引用计数器为0时,就会被销毁并释放内存。而实际上他不是这么的简单粗暴,因为反复的创建和销毁会使程序的执行效率变低。Python中引入了“缓存机制”机制。
例如:引用计数器为0时,不会真正销毁对象,而是将他放到一个名为 free_list
的链表中,之后会再创建对象时不会在重新开辟内存,而是在free_list中将之前的对象来并重置内部的值来使用。
- float类型,维护的free_list链表最多可缓存100个float对象。
v1 = 3.14 # 开辟内存来存储float对象,并将对象添加到refchain链表。
print( id(v1) ) # 内存地址:4436033488
del v1 # 引用计数器-1,如果为0则在rechain链表中移除,不销毁对象,而是将对象添加到float的free_list.
v2 = 9.999 # 优先去free_list中获取对象,并重置为9.999,如果free_list为空才重新开辟内存。
print( id(v2) ) # 内存地址:4436033488
# 注意:引用计数器为0时,会先判断free_list中缓存个数是否满了,未满则将对象缓存,已满则直接将对象销毁。
- list类型,维护的free_list数组最多可缓存80个list对象。
v1 = [11,22,33]
print( id(v1) ) # 输出:4517628816
del v1
v2 = ["武","沛齐"]
print( id(v2) ) # 输出:4517628816
- dict类型,维护的free_list数组最多可缓存80个dict对象。
v1 = {"k1":123}
print( id(v1) ) # 输出:4515998128
del v1
v2 = {"name":"武沛齐","age":18,"gender":"男"}
print( id(v1) ) # 输出:4515998128
- tuple类型,维护一个free_list数组且数组容量20,数组中元素可以是链表且每个链表最多可以容纳2000个元组对象。元组的free_list数组在存储数据时,是按照元组可以容纳的个数为索引找到free_list数组中对应的链表,并添加到链表中。
v1 = (1,2)
print( id(v1) )
del v1 # 因元组的数量为2,所以会把这个对象缓存到free_list[2]的链表中。
v2 = ("武沛齐","Alex") # 不会重新开辟内存,而是去free_list[2]对应的链表中拿到一个对象来使用。
print( id(v2) )
对于元祖类型,维护free_list,通过索引存放元祖,下标为0存储一个空元祖,下标为1存放含有一个元素的元祖,下标为2存放含有二个元素的元祖,依次类推....,索引[1,19]每个位置可以存放2000个元素。
6 源码分析
详见:https://pythonav.com/wiki/detail/6/88/#2.%20C%E8%AF%AD%E8%A8%80%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90
6.1 两个重要的结构体
#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部分数据;list/set/tuple等由多个元素组成对象创建时都有PyVarObject中的那5部分数据。
6.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; /* Revealed in longintrepr.h */
-
str 类型
typedef struct { PyObject_HEAD Py_ssize_t length; /* Number of code points in the string */ Py_hash_t hash; /* Hash value; -1 if not set */ struct { unsigned int interned:2; /* Character size: - PyUnicode_WCHAR_KIND (0): * character type = wchar_t (16 or 32 bits, depending on the platform) - PyUnicode_1BYTE_KIND (1): * character type = Py_UCS1 (8 bits, unsigned) * all characters are in the range U+0000-U+00FF (latin1) * if ascii is set, all characters are in the range U+0000-U+007F (ASCII), otherwise at least one character is in the range U+0080-U+00FF - PyUnicode_2BYTE_KIND (2): * character type = Py_UCS2 (16 bits, unsigned) * all characters are in the range U+0000-U+FFFF (BMP) * at least one character is in the range U+0100-U+FFFF - PyUnicode_4BYTE_KIND (4): * character type = Py_UCS4 (32 bits, unsigned) * all characters are in the range U+0000-U+10FFFF * at least one character is in the range 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 representation (null-terminated) */ } PyASCIIObject; typedef struct { PyASCIIObject _base; Py_ssize_t utf8_length; /* Number of bytes in utf8, excluding the * terminating \0. */ char *utf8; /* UTF-8 representation (null-terminated) */ Py_ssize_t wstr_length; /* Number of code points in wstr, possible * surrogates count as two code points. */ } PyCompactUnicodeObject; typedef struct { PyCompactUnicodeObject _base; union { void *any; Py_UCS1 *latin1;c Py_UCS2 *ucs2; Py_UCS4 *ucs4; } data; /* Canonical, smallest-form Unicode buffer */ } 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;
过常见结构体可以基本了解到本质上每个对象内部会存储的数据。
扩展:在结构体部分你应该发现了
str类型
比较繁琐,那是因为python字符串在处理时需要考虑到编码的问题,在内部规定(见源码结构体):-
字符串只包含ascii,则每个字符用1个字节表示,即:latin1
-
字符串包含中文等,则每个字符用2个字节表示,即:ucs2
-
字符串包含emoji等,则每个字符用4个字节表示,即:ucs4
-