视频地址:https://www.bilibili.com/video/BV1F54114761?p=6&spm_id_from=pageDriver
Python内存管理
python内存管理:引用计数器为主,标记清除和分代回收为辅 + 缓存机制
引用计数器
环状双向链表refchain
在python程序中创建的任何对象都会放在refchain链表中。
name = “ wupeiqi ”
age = 18
hoobby = {“ basketball”, “ beauty ”}
内部会创建一些数据【上一个对象,下一个对象、类型、引用个数】
name = “ wupeiqi ”
new = name
内部会创建一些数据【上一个对象,下一个对象、类型、引用个数、val = 18】
age = 18
内部会创建一些数据【上一个对象,下一个对象、类型、引用个数、items = 元素、元素个数】
hobby = {“ basketball”, “ beauty ”}
C源码
在C源码中如何体现每个对象中都有相同的值:PyObject结构体(4个值:_ob_next _ob_prev ob_refcnt ob_type)
有多个元素组成的对象:PyVarObject结构体(4个值+ob_size)
类型封装结构体
如上图,每个类型都有一个结构体封装,除了之前的两种(4个值和5个值)的结构体,还添加了相应的参数,举例:
data = 3.14
内部会创建
_ob_next = refchain中的下一个对象
_ob_prev = = refchain中的上一个对象
ob_refcnt = 1
ob_type = float
ob_fval = 3.14
引用计数器
vl = 3.14
v2 = 999
v3 = (1,2,3)
当python程序运行时,会根据数据类型的不同找到其对应的结构体,根据结构体中的字段进行创建,然后将对象添加到refchain中
在C源码中有两个关键的结构体:PyObject PyVarObject
每个对象中有 ob_refcnt ,他就是计数器,值默认为1,当有其他变量引用对象时,引用计数器会发生变化。
举例:
a = 99999 #(ob_refcnt = 1)
b = a #(ob_refcnt = 2)
del b #(ob_refcnt = 1)
del a #(ob_refcnt = 0)
当引用计数器为0,说明不在使用这个对象了,将其归为垃圾,进行回收。
回收就是将对象从refchain中移除;然后将对象销毁,释放内存
引用计数器的bug
v1 = [11,22,33] # refchain中创建一个列表对象,引用计数器为1
v1 = [1,2,3] # refchain中创建一个列表对象,引用计数器为1
v1.append(v2) #用计数器为2
v2.append(v1) #用计数器为2
del v1 #用计数器为1
del v2 #用计数器为1
可以发现,尽管v1 v2已经删除,但是引用计数器仍然为1,会占用内存。
标记清除
目的:为了解决引用计数器循环引用的不足
实现:在python的底层在维护一个链表,链表中专门放那些可能存在循环引用的对象(list/tuple/dict/set)
在python内部某种情况下触发,会去扫描可能存在循环引用的链表中的每个元素,检查是否有循环引用,如果有则让双方的引用计数器各自减1,如果是0进行垃圾回收。
问题:
- 什么时候扫描?
- 可能存在循环引用的链表扫描代价大,耗时久
转自:vhttps://blog.csdn.net/weixin_39931923/article/details/111428919
分代回收
将可能存在循环引用的对象分成三个链表:
- 0代:0代中对象个数达到700个扫描一次
- 1代:0代扫描10次,则1代扫描一次
- 2代:1代扫描10次,则2代扫描一次
举例:
创建一个list,将对象加入refchain中,同时加到0代中,当0代满700后进行扫描,将垃圾回收,不是垃圾的对象放到1代,以此类推。
总结
- 在python中维护了一个refchain双向环形链表,这个链表中存储着创建的所有对象,每种类型的对象都有一个ob_refcnt引用计数器,引用个数会随对象引用次数增减,最后当引用计数器为0时进行垃圾回收(对象销毁,回归内存)
- 但是在python中,对于那些由多个元素组成的对象可能会存在循环引用的问题,为了解决这个问题,python引入了标记清除和分代回收,在内部维护了4个链表:refchain、0代、1代2代
- 在源码内部,当达到各自阈值时,就会触发扫描,进行标记清除
优化机制:python缓存
池(int/string等)
为了避免重复创建和销毁一些常见对象,会维护一个池,举例:
启动解释器时,python内部帮我们创建:-5、-4、…、257等数字,
v1 = 7 #内部不会开辟内存,直接去池中获取
v2 = 9 #内部不会开辟内存,直接去池中获取
v3 = 9
print(id(v3),id(v2)) # v2 v3都来自于小数据池,输出地址相同
free_list(float/list/tuple/dict)
当一个对象的引用计数器为0,理论上应该回收,但在python内部不会回收它,而是将其加入free_list中当做缓存,以后在创建这个对象时,不在重新开辟内存,而是直接使用free_list,举例:
v1 = 3.14 # 开辟内存,创建对象
del v1 # 将v1从refchain中移除,讲对象添加到free_list
v9 = 99.9 # 不回重新创建内存开辟内存,去free_list中获取对象,对象内部数据初始化,再放到refchain中。缓冲池满了再进行销毁