• 你应该了解的python 垃圾回收机制


    引用计数器回收

    一个对象,会记录着自身被引用的个数
    每增加一个引用,这个对象的引用计数会自动+1
    每减少一个引用,这个对象的引用计数会自动-1

    查看引用计数

    import sys sys.getrefcount(对象)

    关于getrefcount:

    getrefcount(object) -> integer
    Return the reference count of object. The count returned is generally
    one higher than you might expect, because it includes the (temporary)
    reference as an argument to getrefcount().

    注意下加粗的两个地方:

    1. 需要获取一个对象的引用次数,而非一个类

    2. 由于你通过getrefcount引用了这个对象,所以总引用次数比实际多1

    来个例子看看吧

    import sys
    class Uranus:
        pass
    u1 = Uranus()
    print(sys.getrefcount(u1)-1) # 1
    u3 = u2 = u1
    print(sys.getrefcount(u1)-1) # 3
    del u3
    print(sys.getrefcount(u1)-1) # 2
    del u2
    print(sys.getrefcount(u1)-1) # 1
    del u1
    print(sys.getrefcount(u1)-1) # NameError: name 'u1' is not defined

    引用+1场景

    1. 对象被创建会 +1
      u1=Uranus()

    2. 对象被引用会 +1
      u2=u1

    3. 对象作为参数传入到一个函数中会 +2

    import sys
    class Uranus:
        pass
    u1 = Uranus()
    def func(obj):
        print(sys.getrefcount(obj)-1)
    func(u1)

    为什么会+2呢?python3的有点难理解,但是Python2的可以直接打印一下
    print([(i,getattr(func,i)) for i in dir(func)])
    可以看到__globals__he funcglobals__都存在u1

    4. 对象作为一个元素,存储在容器中会 +1
    list1=[u1]

    引用-1的场景

    1. 对象被销毁
      del u1

    2. 对象被赋予新的值
      u1=Uranus() u1=1

    3. 一个对象离开他的作用域

      如上面对象传入某个函数中,当函数执行完成后,引用会立即销毁

    4. 对象所在的容器被销毁
      list1=[u1] del list1

    特殊场景-循环引用问题

    何为循环引用?怎么去计算?
    此时我们需要使用一个模块objpraph

    Count objects tracked by the garbage collector with a given class name.

    import objgraph # 需要单独下载
    class Person:
        pass
    class Animal:
        pass
    print(objgraph.count("Person")) # 0
    print(objgraph.count("Animal")) # 0
    
    P = Person()
    A = Animal()
    print(objgraph.count("Person")) # 1
    print(objgraph.count("Animal")) # 1
    
    P.pet = A
    A.master = P
    del P
    del A
    # 正常情况下,如果删除了P和A,应该为0,但由于循环引用,结果为1
    print(objgraph.count("Person")) # 1
    print(objgraph.count("Animal")) # 1

     那这样不是GG思密达了?那怎么阔能!!!真这样的话Python还有谁用呢?

    垃圾回收机制

    从经历过引用计数器机制仍未被释放掉的对象中,找到循环引用并删除相关对象

    何时启动垃圾回收

    不是说你创建了一个变量,就会马上开始垃圾回收的!
    需要你代码中,新增对象-消亡对象阈值达到某一个零界点是才会启动垃圾回收
    如何查看这个阈值呢?需要引入GC模块

    import gc
    print(gc.get_threshold())
    
    output:
    (700, 10, 10)

    代码的结果是一个元组,后面两位之后说,700代表python设置的阈值

    怎么找到循环引用

    1. 搜集所有容器对象,通过双向列表进行引用

    容器对象:list couple dict ...
    非容器对象:a=10 ...

    1. 针对每一个容器对象,通过一个变量gc_refs来记录当前对应的引用计数

    2. 对于每一个容器对象,找到它引用的容器对象,并将这个容器对象的引用计数-1

    3. 如果经历以上三次,如果一个容器对象的引用次数为0,就代表可以被回收了

    通过上面的循环引用查找,也许有的人认为比较简单,但是如果一个大的项目,存在着成千上万的容器对象,这么每次去检测岂不是要累死?
    python考虑到此处,所以创建了一套回收机制,叫做分代回收

    分代回收

    何为分代回收,当第一次检测完成后,部分的容器对象没有为0,则将其从0代移动至1代对象中,当检测10次后,在第11次时,会再次扫描0代1代的对象,当101次,即1代对象也被检测了10次,仍存在未被回收的容器对象时,会将器移动至2代中。多像爷爸孙....
    至于为什么是10,就是刚才我们查看gc.get_threshold()得到的元组后两个字段。
    当然我们也可以进行手动设置:
    gc.set_threshold(100,5,5)
    但劝你没事儿还是别瞎折腾....

    垃圾回收的开启、关闭、状态查询

    垃圾回收机制,默认都是开启的,当然我们可以进行调整
    gc.disable() gc.enable() gc.isenabled()

    如何手动触发垃圾回收

    通过gc.collect()手动触发垃圾回收。
    此处需要注意一点,即便目前的垃圾回收机制处于关闭状态,一样可以手动触发。

    如何避免循环引用

    使用弱引用模块

    import weakref
    A.master = weakref.ref(P) # 此时会生成弱引用

    何为弱引用,即在引用的时候,不会是计数器+1
    但是weakref.ref只是针对单个引用的,如果是多个呢?
    使用:

    weakref.WeakKeyDictionary
    weakref.WeakValueDictionary
    weakref.WeakSet

    手动使引用计数-1

    A.master = None
    通过重新赋值的方法,是不是也可以达到这种方式呢?

    The End

    OK,今天的内容就到这里,如果觉得有帮助,欢迎将文章或我的微信公众号【清风Python】分享给更多喜欢python的人,谢谢。

    作者:清风Python

  • 相关阅读:
    硬盘安装FreeBSD 6.1release步骤
    Centos,bash: service: command not found
    test1tset
    ubuntu只能访问部份网站的处理方法
    lamp lnmp
    调查用QQ企业邮箱的smtp需要添加spf1
    asp.net文件下载
    FreeBSD更新ports源
    ubuntu 12.10 安装 fcitx 五笔
    csh/tcsh颜色配置
  • 原文地址:https://www.cnblogs.com/2020-zhy-jzoj/p/13165704.html
Copyright © 2020-2023  润新知