python的垃圾回收机制
内容整理自视频https://www.bilibili.com/video/BV1F54114761?p=2&spm_id_from=pageDriver
引用计数器为主,标记清除和分代回收为辅助.
1.应用计数器
1.1 环状的双向链表 refchain
在python程序中,创建的任何对象都会放在refchain中.
当创建 #name='xxx'时, python 内部会通用创建一些数据
[上一个对象,下一个对象,类型,引用个数]
不同数据类型会创建一个相对的值
Age=18 会创建
[上一个对象,下一个对象,类型,引用个数,val=18]
列表 会创建
[上一个对象,下一个对象,类型,引用个数,items=元素,元素个数]
底层源码
在C源码中体现每个对象中都有的相同的值:PyObject结构体(4个值:上一个,下一个,类型,引用个数)
体现由多个元素组成的对象:PyObject结构体(4个值)+ob_size.至少封装五个值.
1.2 详细解析类型封装结构体
屏幕剪辑的捕获时间: 2021/4/19 20:50
1.3引用计数器
V1=3.14
V2=999
V3=(1,2,3)
当python程序运行时 会根据数据类型的不同找到其对应的结构体,根据结构体中的字段,创建相关的数据,然后将对象添加到refchain双向链表中.
源码中有两个关键的结构体:PyObject(公共)和PyVarObject(多个元素).
每一个对象中 都有 ob_refcnt 就是引用计数器,默认为1,当有其他变量引用该对象时,则引用计数器会发生变化
加一:a=33,b=a 则a的引用计数器 加一
减一:del b 则a的引用计数器 减一
当引用计数器为0时,意味着改对象不再被使用了,则证明该对象为垃圾,然后进行回收,
回收:1.将对象从refchain移除,2.将对象进行销毁,将内存归还给系统
1.4引用计数器的循环引用问题
循环引用 交叉感染
执行append后 v1 v2 的引用计数器 值均为2
执行del后 v1 v2 的应用计数器 均为1
则 v1 v2 永远不会被销毁,
这样的代码多了,内存会被沾满,发生爆栈,内存泄露.重启可以解决
2.标记清除
目的:为了解决引用计数器的循环引用不足.
实现:在python底层 再维护一个链表,链表中专门放那些可能存在循环引用的对象(列表,字典,元组,集合)
在python内部,某种情况下触发,扫描可能存在循环引用的链表中的每一个元素,检查是否有循环引用,如果有则让双方的引用计数器都减一,如果为0,则是垃圾,进行回收.
会有两个问题发生
1.我们不知道什么时候扫描
2.扫描代价大.每次扫描耗时久.
3.分代回收
四个链表搞定一切
1.将可能存在循环引用的对象,维护成3个列表:
0代:0代中的对象个数达到700个则扫描一次0代,
1代:0代扫描10次,1代扫描一次,
2代:1代扫描10次,2代扫描一次.
扫描过程中,引用计数器减一,如果引用计数器为0,则是垃圾,进行回收.
如果不是垃圾,则从0代升级到1代.只有0代阈值是对象个数,1代,2代都是前代的扫描次数
4.小结
在python中,维护了一个refchain的双向环状链表,这个链表中存储程序中创建的所有对象,
每种类型对象中都有一个ob_refcnt的引用计数器值.当有引用时,引用计数器的值加一,有删除时
引用计数器的值会减一,当引用计数器的值,变为0时,进行垃圾回收(对象销毁,refchain中移除).
但是,在python中,对于那些可以有多个元素组成的对象可能存在循环引用的问题,为了解决这个问题,python又引入了标记清除和分代回收,在其内部,共维护四个链表,分别是:
1.refchain 维护所有对象的
2.2代 2代扫描10次扫描一次
3.1代 0代扫描10次扫描一次
4.0代 当存储达到700个对象时扫描
在源码内部,当达到各自的阈值时,会触发扫描链表,进行标记清除的动作(有循环引用,则格子减一)
但是,在源码内部在上述的流程中提出了优化机制.
5.Python缓存 详见
https://pythonav.com/wiki/detail/6/88/
5.1 池(int类型,字符串)
为了避免重复创建和销毁一些常见对象,维护一个池,
假设创建 a=7,b=9, 按理说 会创建两个对象 放入refchain中, 但实际是
在python解释器启动时候,帮我们自动创建 -5到256的值全部创建.
在程序执行a=7时,不会开辟新内存了,直接去池里取.
5.2free_list机制(float/list/tuple/dict)
当一个对象引用计数器为0时,按理说应该回收,实际内部并不会回收,而是将对象
而是将对象添加到 free_list链表中当缓存.以后再去创建对象时,不会重新开辟内存
而是直接使用free_list
Free_list 会有缓冲大小 list和dict最多缓存80个对象.tuple最多缓存20个对象
str有自己的unicode_latin1[256]链表 将所有的ascii字符缓存起来.
字符串 有助流机