Python垃圾回收机制

Python中的垃圾回收是以引用计数为主,标记-清除和分代收集为辅。引用计数的缺陷是循环引用的问题

1. 垃圾回收机制

在Python中,如果一个对象的引用计数是0,Python虚拟机就会回收这个对象的内存

class Test():
    def __init__(self):
        print("object born, id is:{}".format(hex(id(self))))

    def __del__(self):
        print("object del, id is:{}".format(hex(id(self))))

def func():
    while True:
        t1 = Test()
        del t1

# 执行func函数,会循环输出的结果,而且进程占用的内存基本不会变动

object born, id is:0x7f85c6282fd0
object del, id is:0x7f85c6282fd0

解析:
t1 = Test() 会创建一个对象,放在内存中,t1变量指向这个内存,这个时候内存的引用计数是1
del t1后,t1变量不在指向这个内存,所以这块内存的引用计数减一,等于0,所以就销毁这个对象,释放内存

  1. 导致引用计数+1的情况

    • 对象被创建:a = 1
    • 对象被引用:b = a
    • 对象被作为参数,传入到函数中:func(a)
    • 对象作为元素,存储在容器中:list1 = (a, a)
  2. 导致引用计数-1的情况

    • 对象的别名被显式销毁:del a
    • 对象的别名被赋予新的对象:a = 24
    • 一个对象离开它的作用域,例如函数执行完毕,函数中的局部变量(全局变量不会)
    • 对象所在的容器被销毁,或从容器中删除对象

2. 标记-清除

主要用于处理循环引用的问题,只有容器对象(list, dict, tuple, instance)才会出现循环引用状态

def func2():
    while True:
        t1 = Test()
        t2 = Test()
        t1.t = t2
        t2.t = t1
        del t1
        del t2
		
# 执行func2(),进程占用会不断增大
object born, id is:0x7f85c6282fd0
object del, id is:0x7f85c6282fd0

解析:
创建了t1,t2后,t1和t2对于的内存的引用计数都是1,执行t1.t = t2和t2.t = t1后,这两块内存的引用计数变成2,del t1之后,内存引用变成1,由于不是0,所以t1的对象不会被销毁,所以t2内存的引用依旧是2,在del t2之后,同理。虽然她们的两个对象都可以被销毁,但是由于循环引用,导致垃圾回收器不会回收它们,导致内存泄露。image

处理过程:

  1. 将所有容器放到一个双向链表中,这些对象为0代(零代算法)
  2. 循环遍历链表,如果被本链表内的对象引入,自身的被引用数减一,如果被引用数为0,则该对象触发计数回收条件,被回收
  3. 未被回收的对象,升级为1代

触发条件:
因为循环引用的原因,并且因为的程序使用了一些比其他对象存在时间更长的对象,从而被分配对象的计数与被释放对象的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动,并触发零代算法,释放对象,并将剩余对象移动到下一代链表。
由于时间到推移,一代链表越来越多,并触发gc阈值,同样会对一代链表进行标记清除操作,然后将剩余活跃对象升级二代
imageimage

3. 垃圾回收

发现并处理不可达的对象,垃圾回收=垃圾检查+垃圾回收,gc模块的一个主要功能就是解决循环引用的问题

def func3():
    t1 = Test()
    t2 = Test()
    t1.t = t2
    t2.t = t1
    del t1
    del t2
    print(gc.garbage)
    print(gc.collect())   # 显式执行垃圾回收
# 输出
gc: uncollectable <ClassA instance at 0230E918>
gc: uncollectable <ClassA instance at 0230E940>
gc: uncollectable <dict 0230B810>
gc: uncollectable <dict 02301ED0>
object born,id:0x230e918
object born,id:0x230e940

解析:
gc模块有一个自动垃圾回收的阈值,必须要import gc模块,并且is_enable()=True才会开启自动垃圾回收
何时触发:

  1. 被引用为0时,立即回收当前对象
  2. 达到垃圾回收的阈值,触发标记-清除
  3. 手动调用gc.collect()
  4. Python虚拟机退出时

4. 应用

  • 项目中避免循环引用
  • 引入gc模块,启动gc模块的自动清理循环引用的对象机制
  • 由于分代收集,所以需要把长期使用的变量集中管理,并尽快移交到二代以后,减少gc检查时的消耗
  • gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义该方法,如果一定要使用,同时导致循环引用,需要代码显式调用gc.garbage里面的对象__del__来打破僵局
posted @ 2022-03-11 15:51  KB、渣科  阅读(202)  评论(0编辑  收藏  举报