一、引用计数器
![C源码-结构体](https://i-blog.csdnimg.cn/blog_migrate/19503a0aa6275dbd24671e514f407abb.png)
1.1、循环双链表
# 环状双向链表-refchain """ 环状双向链表-refchain:创建的任何对象都会放在refchain链表中 { name="huge" age=15 hobby=["篮球","美女","游戏"] } 内部会创建一个结构体【上一个对象,下一个对象,类型,引用个数】。通用存这四个 { name="huge" new=name 引用个数:2 }
1.2、数据封装类
data=3.14
内部会创建:
_ob_next
_ob_prev
ob_refcnt=1
ob_type=float
ob_fval=3.14
1.3、引用计数器
当python运行程序时,会根据数据类型不同找到对应的结构体,根据结构体中的字段来创建相关的数据,然后将对象添加到refchain双向链表中
在C原码中,有两个关键的结构体:PyObject、PyVarObject
PyObject:存储公共的值,每个对象都有
PyVarObject:存储由多个元素组成时,公共的值
每个对象都有ob_refcnt,默认值1;当其他变量引用时,引用计数器变换。
# 引用
a=99 # ob_refcnt=1
b=a # ob_refcnt=2
# 删除引用
del b # b变量删除,b引用对象的计数器-1
由此当一个对象的引用计数器为0时,表示没有人在使用找个对象,这个对象就是垃圾,垃圾回收。
# 回收
1、对象从refchain链表中移除;2、将对象销毁,内存归还
二、标记清除
2.1、循环引用问题&交叉感染
v1 = [1, 2, 3] # v1_refcnt=1 v2 = [4, 5, 6] # v2_refcnt=1 v1.append(v2) # v2_refcnt=2 v2.append(v1) # v1_refcnt=2 del v1 # v1的引用计数器-1 v1_refcnt=1 del v2 # v2的引用计数器-1 v2_refcnt=1 # 此时删除v1,v2,他们的引用计数器=1,不会被清除,就变成了垃圾
2.2、标记清除
# 为了解决引用计数器循环引用的不足
# 实现:在Python底层中在维护一个链表,专门存放可能存在循环引用的对象(列表list,字典dict,元组tuple,集合set)
# 在某种情况触发,回去扫描可能出问题的链表中的每个元素,检查是否有循环引用,有则双方引用计数器都-1,如果是0,则垃圾回收。
三、分代清除
将可能存在循环应用的对象维护成三个联表:
- 0代:0代中对象个数达到700个,扫描一次
- 1代:0代扫描10次,1代扫描一次
- 2代:1代扫描10次,2代扫描一次
将新加的元素添加到0代中,当新加的元素达到700个,扫描一次,将循环引用的对象引用计数器-1,垃圾回收,不是垃圾的放到1代中,并标记1代,0代扫描了一次;循环十次后才会扫描1代,以此类推。
四、小结
在python中维护了一个refchain的双向环状链表,这个链表中存储程序创建的所有对象,每种类型的对象中都有个ob_refcnt引用计数器的值,引用个数 +1、-1,最后当引用计数器变为0时会进行垃圾回收(对象销毁、refchain中移除)。
但是,在python中对于那些可以有多个元素组成的对象可能会存在循环引用的问题,为了解决这个问题python又引入了标记清除和分代回收,在其内部为了4个链表,
refchain
。2代,10寸
。1代,10次
。0代,700个
在源码内部当达到各自的阈值时,就会触发扫描链表进行标记清除的动作 (有循环则各自-1)。
But,源码内部在上述的流程中提出了优化机制
五、Python中的缓存机制
5.1、池(int)
为了避免重复创建和销毁一些常见对象,维护池
# 启动解释器,Python内部帮我们创建一个池:-5,-4,....,257 v1 = 7 ; 内部不会开辟内存,直接去池中获取
5.2、Free_list(float、list、tuple、dict)
当一个对象的引用计数器为0时,应当回收;但是内部不会直接回收,而是将对象添加到free_list链表中当缓存。当再次创建对象时不会重新开辟内存,而是直接使用free_list
当free_list存满时,才会销毁
v1=3.14 # 开辟内存,内部存放结构体中定义的值,并存放到refchain中
del v1 # 删除v1,从refchain中移除,将对象放到free_list中缓存,如果free_list存满,则销毁
v9=99.99 # 不会在开辟内存,而是从free_list中获取对象,并将对象重新赋值,存到refchain中