# 引用计数
Python垃圾回收
主要以引用计数
为主,分代回收
为辅。在 Python 中每一个对象的核心就是一个结构体PyObject
,它的内部有一个引用计数器(ob_refcnt)
。程序在运行的过程中会实时的更新ob_refcnt
的值,来反映引用当前对象的名称数量。当某对象的引用计数值为0
, 那么它的内存就会被立即释放掉。
- 以下情况是导致引用计数加一的情况:
- 对象被创建,例如 a=2
- 对象被引用,b=a
- 对象被作为参数,传入到一个函数中
- 对象作为一个元素,存储在容器中
- 下面的情况则会导致引用计数减一:
- 对象别名被显示销毁 del
- 对象别名被赋予新的对象
- 一个对象离开他的作用域
- 对象所在的容器被销毁或者是从容器中删除对象
- 通过
getrefcount()
来获取引用的对象当前的引用计数
import sys | |
class Person: # 对象被创建 +1 | |
pass | |
p1=Person() # 实例化 +1 | |
p2=p1 # 赋值 +1 | |
print(sys.getrefcount(p1)) | |
# 当前引用计数为 3 |
- 上面 p1 虽然赋值给了 p2 但是不会开辟新的内存空间,通过
id()
来进行查看
import sys | |
class Person: | |
pass | |
p1=Person() | |
p2=p1 | |
print(id(p1)) # 打印结果:1279802481016 | |
print(id(p2)) # 打印结果:1279802481016 |
# 标记清除解决循环引用
Python 采用了
“标记-清除”(Mark and Sweep)算法
,解决容器对象可能产生的循环引用问题。
注意,只有 容器对象
才会产生循环引用的情况,比如 列表
、 字典
、 用户自定义类的对象
、 元组
等。而像 数字
, 字符串
这类简单类型不会出现循环引用。作为一种优化策略,对于只包含简单类型的元组也不在标记清除算法的考虑之列
import objgraph | |
import gc | |
# 循环引用 | |
class Person: | |
pass | |
class Dog: | |
pass | |
p = Person() | |
d = Dog() | |
p.pet = d | |
d.master = p | |
del p | |
del d | |
gc.collect() #手动触发垃圾回收 | |
print(objgraph.count("Person")) # 打印结果:0 | |
print(objgraph.count("Dog")) # 打印结果:0 | |
''' | |
对象间互相引用,导致对象不能通过引用计数器进行销毁 | |
手动触发垃圾回收,挥手循环引用 | |
''' |
# 分代回收
在循环引用对象的回收中,整个应用程序会被暂停,为了减少应用程序暂停的时间,Python 通过
分代回收(Generational Collection)
以空间
换时间
的方法提高垃圾回收效率。
分代回收是基于这样的一个统计事实,对于程序,存在一定比例的内存块的生存周期比较短;而剩下的内存块,生存周期会比较长,甚至会从程序开始一直持续到程序结束。生存期较短对象的比例通常在 80%~90% 之间,这种思想简单点说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集。这样在执行标记 - 清除算法时可以有效减小遍历的对象数,从而提高垃圾回收的速度。
python gc
给对象定义了三种世代 (0,1,2), 每一个新生对象在 generation zero
中,如果它在一轮 gc
扫描中活了下来,那么它将被移至 generation one
, 在那里他将较少的被扫描,如果它又活过了一轮 gc, 它又将被移至 generation two
,在那里它被扫描的次数将会更少。