引用计数器为主,标记清除和分代回收为辅+缓存机制
1、引用计数器
1.1 环状双向链表 refchain
在python 中创建的任何对象都会放在rechain链表中 。
内部会创建一写数据【上一个对象,下一个对象,类型,引用对象,value="王"】 name="王"
在C语言源码中体现相同的值:PyObject结构体(前4个值肯定存在)
有多个元素组成的对象:PyObject结构体(前4个值肯定存在)+ob_size 。
1.2详细解析类型封装的结构体
data=3.14 内部会创建: _ob_next=refchain中的上一个对象 _ob_prevrefchain中的下1一个对象 ob_refcnt=1 ob_type=float ob_fval=3.14
1.3引用计数器
v1=3.14 v2=999 v3=(1,2,3)
当python程序运行时,会根据数据类型的不同找到其对应的结构体,根据结构体中的字段来创建相关的额数据,然后讲对象添加到refchain双线链表中。
在C源码中有两个关键的结构体:PyObject、PyVarObject(多个元素组成的对象基于这个实现)
每个对象中有ob_refctn就是引用计数器,默认值为1,当有其他变量引用对象时,应用计数器就会发生变化
删除其余变量是计数器就会-1。
当引用计数器为0时,没有人使用这个对象,这个对象就是垃圾,垃圾回收。
回收:1.将对象从refchain链表移除 2.将对象销毁,内存归还。
1.4 循环引用的问题
交叉感染
v1=[1,2,3] 计数器为1 v2=[4,5,6] 计数器为1 v1.append(v2) 计数器为2 v2.append(v1) 计数器为2 del v1 计数器为1 del v2 计数器为1 计数器不会变成0,就无法进行垃圾回收
删除之后计数器不会变成0;也没有在有变量引用,没法进行垃圾回收
2、标记清除
目的:为了解决引用计数器循环引用的不足。
实现:在python 底层,再维护一个链表,链表中专门放那些可能存在循环应用的对象(list/tuple/dict/set)。
内存就会有两个链表
在python内部在某种情况下会去扫描可能存在循环引用的链表中的每个元素,检查是否有循环引用,如果有则让双方的引用器-1;如果为0则回收垃圾。(标记清除)
1.什么时候扫描?
2.可能存在循环引用的链表扫描代价较大,每次扫描耗时久?
3、分代回收
将可能存在循环引用的对象维护成3个链表
0代:0代中的对象个数到达700个扫描一次
1代:0代扫描10次,1代扫描一次
2代:1代扫描10次,2代扫描一次
最开始先往0代加入,到达700个对象扫描,垃圾回收后剩下的放入1代
4、小结
在python中有维护一个refchain的双向环状链表,存储创建的所有对象,每组对象中都有一个ob_refcnt引用计数器的值,引用个数+1,-1。当引用计数器变成0时就会进行垃圾回收(对象销毁、refchain中移除)
但是,在python中对于那些可以有多个元素组成的对象可能会存在循环引用的问题,未来解决这个问题,python引入了标记清除和分代回收,在其内部建立了4个链表,refchain、0代,1到,2代。在源码内部当达到各自的阈值时,就会触发扫描链表进行标记清除的动作(有循环各自-1)
But,源码内部在上述的流程中提出了优化机制
5、python缓存
5.1、池
未来避免重复的创建和销毁一些常见对象,维护池
#启动解释器时,python内部会创建:-5,-4 …… 257 v1=7 #这时内部不会开辟内存,直接去池中取,超出上面的值才会创建对象 v2=9
5.2、free_list
(float、list、tuple、dict)
当一个对象的引用计数器为0时,按理说应该回收,但内部不会直接回收,而是将对象添加到free_list链表中当缓存,以后再去创建对象时,不再重新开辟内存,而是直接使用free_list
v1=9.0 #开辟内存,内部存储结构体中定义那几个值,并存到refchain中 del v1 #refchain中移除,添加到free_list中 v2=3.3 #不会再创建一个对象内存,直接去free_list中获取并初始化,在放到refchain中 #v1\v2类型要相同