Python内存管理&垃圾回收机制
引用计数器为主,标记清除和分代回收为辅 (循环垃圾回收器) + 缓存机制
一、引用计数器
1、环状双向链表 refchain
- 在python程序创建的任何对象都会放在rechain双向链表中。
name = '七落'
age = 18
hobby = ['篮球', '美女']
# 内部会创建一些数据【上一个对象、下一个对象、类型、引用个数】
name = '七落'
# 内部会创建一些数据【上一个对象、下一个对象、类型、引用个数、val = 18】
age = 18
# 内部会创建一些数据【上一个对象、下一个对象、类型、引用个数、items= '元素'、元素个数】
hobby = ['篮球', '美女']
2、类型封装结构体
data = 3.14
# 内部会创建:
_ob_next = refchain中下一个对象
_ob_prev = refchain中上一个对象
ob_refcnt = 1 # 引用个数
ob_type = float
ob_fval = 3.14
- 前四个都是对象中共有的值:PyObject结构体(四个值)
3、引用计算器
v1 = 3.14
v2 = 999
v3 = (1,2,3)
当python程序运行时,会根据数据不同的类型找到不同的结构体,根据结构体中的字段来进行创建相关的数据,然后将创建的对象添加到refchain双向链表中。
在C源码中有两个关键结构体:PyObject(通用)、PyVarObject。
每个对象中有的ob_refcnt 就是引用计数器,值默认为1,当有其他变量引用对象时,引用计数器就会加1。
- 引用:
a = 9999 # 创建对象时初始化引用计数器为1
b = a # 9999的引用计数器 + 1
- 删除引用
a = 9999
b = a
del b # b变量删除,b对应对象(9999)引用计数器 -1
del a # a变量删除,a对应对象(9999)引用计数器 -a
# 垃圾回收:当一个对象的引用计数器为0的时候,意味着没有人使用这个对象了,这个对象就是一个垃圾,垃圾回收
# 回收:1.对象从refchain双向链表中移除 2.将对象销毁,内存归还
增加引用计数器:
-
对象被创建时
-
另外的别名被创建
-
被作为参数传递给函数(新的本地引用)
-
成为容器对象的一个元素
减少引用计数器:a = 10
- 对象的引用被销毁时 (del a)
- 当变量被赋值给另外的对象 (a = 10,b = a,a = 20)
二、标记清除
1、循环引用问题
循环引用&交叉感染
v1 = [1,2,3] # 在refchain创建列表对象v1,初始化引用计数器为1
v2 = [4,5,6] # 在refchain创建列表对象v2,初始化引用计数器为1
v1.append(v2) # 把v2追加到v1中,则v2对应的[4,5,6]对象的引用计数器+1,最终为2
v2.append(v1) # 把v1追加到v2中,则v1对应的[1,2,3]对象的引用计数器+1,最终为2
del v1 # 引用计数器为-1
del v2 # 引用计数器为-1
- 引用计数器并不能为0,如果只是通过引用计数器的话并不能销毁对象,归还内存
2、标记清除
目的:为了解决引用计数器的循环引用的不足。
实现: 通过在python的底层,再维护一个链表,在链表中专门存放那些可能存在循环引用的对象。(list、tuple、set、dict)
在python内部某种情况
情况下触发,回去扫描可能存在循环应用的链表
,检查是否有循环引用,如果有则让双方的引用计数器 -1,如果引用计数器为0则垃圾回收。
问题:
- 什么时候扫描?
- 可能存在循环引用的链表 扫描代价大,每次扫描耗时久。
三、分代回收
将可能存在的循环引用的对象维护成3个链表:
- 0代 : 0代中的对象个数达到700个扫描一次
- 1代: 0代扫描10次,则1代扫描一次。
- 2代: 1代扫描10次,则2代扫描一次。
比如:当0代中的对象个数达到700时候扫描一次,将存在的循环引用对象的引用计数器-1,若是引用计数器为0则垃圾回收,否则将对象存放到1代中管理。当0代扫描10次后,1代扫描一次,同样存放到2代中管理。
四、小结
在python中维护了一个refchain双向环状链表,这个链表中存储了程序创建的所有对象,每种类型的对象中都有一个ob_refcnt引用计数器的值,引用个数+1 或 - 1,当引用计数器值为0时会进行垃圾回收。(对象销毁,从refchain链表中移除)
但是,在python中对于那些可以有多个元素组成的对象可能会存在循环引用问题,为了解决这个问题,python又引入了标记清除和分代回收,在其内部维护了4个链表:
- refchain
- 0代:700个对象
- 1代:0代扫描10次
- 2代:1代扫描10次
在源码内部达到各自的阈值后,就会触发扫描链表进行标记清除的动作(有循环引用则引用计数器-1)、
五、Python缓存
1、池(int)
为了避免重复创建或销毁一些常见的对象,维护池。
# 在启动解释器的时候,python内部帮我们创建:-5、-4...257
v1 = 10 # 内存不会开辟内存,直接去池里面取
v2 = 16 # 内存不会开辟内存,直接去池里面取,引用计数器的本来已经被初始化为1了
2、free_list(float/list/tuple/dict)
当一个对象的引用计数器为0时,按理说应该回收。但是内部不会直接回收,而是将对象添加到free_list链表中缓存。以后再创建对象时,不再重新开辟新的空间,而是直接使用free_list。
v1 = 3.14 # 开辟内存,内部存储结构定义值,并存储到rechain结构中
del v1 # 从refchain中移除,把对象添加到 free_list中,free_list满了则销毁
v2 = 9.99 # 不再重新开辟空间,去free_list中获取对象,对象内部数据初始化,再放到rechain中
# 那么v1 与 v2 可能有相同的id内存地址
参考文档:
https://www.cnblogs.com/wupeiqi/articles/11507404.html
http://www.wklken.me/posts/2015/09/29/python-source-gc.html
标签:垃圾,v1,Python,v2,链表,对象,计数器,引用,内存 From: https://www.cnblogs.com/luoluoange/p/17739346.html