• Python 垃圾回收机制


    Python 垃圾回收(Garbage Collection)

    Python 垃圾回收机制主要为 引用计数标记-清除分代收集 辅助

    引用计数

    在每次分配和释放内存的时候, 加入管理引用计数的动作
    当引用计数器为零时,该内存就会被销毁

    特点

    优点:简单、高效、实时(将处理垃圾时间分摊到运行代码时,而不是等到一次回收)
    缺点:无法解决循环引用的问题

    工作原理

    引用计数 + 1

    进行以下操作时,引用计数会 + 1:

    1. 创建对象
    2. 引用对象

    引用计数 - 1

    进行以下操作时,引用次数 - 1:

    1. 被销毁
    2. 指向其他对象

    查看引用次数

    可以使用 sys.getrefcount 函数查看引用次数
    示例:

    from sys import getrefcount
    a = 'cnblogs.com/dbf-/'
    print(getrefcount(a))
    b = a
    print(getrefcount(a))
    del b
    print(getrefcount(a))
    

    输出结果:

    4
    5
    4
    

    循环引用

    string、number 对象并不会产生循环引用,但是 list、dict 等内部可以引用其他对象的就会产生
    示例:

    a = list()
    b = dict()
    a.append(b)
    b['a'] = a
    del a
    del b
    

    外部并没有引用 a、b 两个对象,但是它们的应用次数却同时加一,即使将 a、b 删除引用次数依然不为 0
    所以无法通过引用计数发现并清除

    标记-清除 (Mark-weep)

    标记-清除算法分为两个阶段:

    1. 标记活动对象
    2. 回收非活动对象

    有两个链表,root 和 unreachable
    root 为全局变量,unreachable 为非活动对象
    将非活动对象添加到 unreachable 链表中,之后进行删除

    特点

    清除对象前需要扫描整个堆内存

    工作原理

    从 root 链表出发,按照引用方向进行遍历,并将引用次数减一,如果减到 0 且没有被其他对象引用,则

    回收示例:

    a = list()
    b = list()
    a.append(b)
    b.append(a)
    del a
    del b
    

    此时 a、b 的引用计数均不为 0,所以不会被引用计数机制回收
    开始标记时,首先找到 a,因为 a 中有对 b 的引用,所以将 b 的引用次数减一,之后通过引用到达 b,由于 b 中有对 a 的引用,所以将 a 的引用次数减一
    这时 a、b 引用次数均为 0,所以被添加到 unreachable 链表中清除

    不回收示例:

    a = list()
    b = list()
    a.append(b)
    b.append(a)
    del a
    

    此时 a、b 的引用计数均不为 0,所以不会被引用计数机制回收
    开始标记时,首先找到 a,因为 a 中有对 b 的引用,所以将 b 的引用次数减一,之后通过引用到达 b,由于 b 中有对 a 的引用,所以将 a 的引用次数减一
    这时 a 引用次数均为 0,被添加到 unreachable 链表;由于 b 不为 0 且引用 a,所以 a 被从 unreachable 链表中取出

    分代收集

    Python 将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python 将内存分为了 3 代,分别为 零代、一代、二代(generation 0, 1, 2)
    他们对应的是 3 个链表,它们的垃圾收集频率与对象的存活时间的增大而减小
    年轻代链表的总数达到上限时,Python 垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推
    老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内
    同时,分代回收是建立在标记清除技术基础之上,分代回收同样作为 Python 的辅助垃圾收集技术处理那些容器对象

    弱代假说

    年轻的对象更容易死掉,年老的对象容易存在更长的时间
    因此将新创建的对象放入零代链表,零代进行扫描的频率更高,扫描后依然存活的对象放入高一级链表,越高级的链表扫描频率越低

    零代链表

    每当创建一个对象时,都会将它加入零代链表
    零代链表中都是最年轻的对象

    GC 阈值

    如果不存在循环引用,则创建的对象最后都会被回收,也就是创建对象的数量等于释放的数量
    但是出现循环引用就会使得创建的数量大于释放的,一旦这个差值超过一定数量,就会触发分代回收机制

    回收触发时机

    1. 调用 gc.collect()
    2. 到达阈值
    3. 退出程序
  • 相关阅读:
    译文高效的JavaScript.
    JavaScript 全半角转换
    js表单验证
    Js事件大全
    Javascript下的urlencode编码解码方法decodeURIComponent()
    加速Javascript:DOM操作优化
    javascript验证日期的函数
    javascript里面的小数计算出现近似值的解决办法
    【busybox】busybox使用总结 01
    Assemble 汇编语言的种类
  • 原文地址:https://www.cnblogs.com/dbf-/p/11928117.html
Copyright © 2020-2023  润新知