zoukankan      html  css  js  c++  java
  • Python的垃圾回收机制

    Python的垃圾回收机制

    python采用的是引用计数机制为主,标记-清除分代收集(隔代回收)两种机制为辅的策略。

    现在的高级语言如java,c#等,都采用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的方式。自己管理内存极其自由,可以任意申请内存,但如同一把双刃剑,为大量内存泄露,悬空指针等bug埋下隐患。
    对于一个字符串、列表、类甚至数值都是对象,且定位简单易用的语言,自然不会让用户去处理如何分配回收内存的问题。
    python里也同java一样采用了垃圾收集机制,不过不一样的是:
    python采用的是引用计数机制为主,标记-清除和分代收集(隔代回收)两种机制为辅的策略。

    1、引用计数机制

    引用计数法机制的原理是:
    每个对象维护一个ob_ref字段,用来记录该对象当前被引用的次数,
    每当新的引用指向该对象时,它的引用计数ob_ref加1,
    每当该对象的引用失效时计数ob_ref减1,
    一旦对象的引用计数为0,该对象立即被回收,对象占用的内存空间将被释放。
    它的缺点是需要额外的空间维护引用计数,这个问题是其次的,
    不过最主要的问题是它不能解决对象的“循环引用”,
    因此,也有很多语言比如Java并没有采用该算法做来垃圾的收集机制。
    

    python里每一个东西都是对象,它们的核心就是一个结构体:PyObject

    PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少

    引用计数为0时,该对象生命就结束了。

    引用计数机制的优点

    1、简单

    2、实时性:一旦没有引用,内存就直接释放了,不用像其他机制得等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。

    引用计数机制的缺点

    1、维护引用计数消耗资源

    2、循环引用

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

    • 对象被创建,例如a=23
    • 对象被引用,例如b=a
    • 对象被作为参数,传入到一个函数中,例如func(a)
    • 对象作为一个元素,存储在容器中,例如list1=[a,a]

    1.2 导致引用计数-1的情况

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

    1.3 循环引用导致内存泄露

    def f2():
        '''循环引用'''
        while True:
            c1=A()
            c2=A()
            c1.t=c2
            c2.t=c1
            del c1
            del c2
    
    • 创建了c1c2后,这两个对象的引用计数都是1,执行c1.t=c2c2.t=c1后,引用计数变成2.
    • del c1后,内存c1的对象的引用计数变为1,由于不是为0,所以c1的对象不会被销毁,同理,在del c2后也是一样的。
    • 虽然它们两个的对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。

    1.4 小整数池

    当python启动的时候,会自动定义[-5,256]之间的整数变量,他们的内存空间已经写死了,当重复使用这个范围内的数时,不会开辟新的内存空间来存放数据。

    2、标记-清除

    『标记清除(Mark—Sweep)』算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。

    它分为两个阶段:

    第一阶段是标记阶段,GC会把所有的『活动对象』打上标;

    第二阶段是把那些没有标记的对象『非活动对象』进行回收。

    那么GC又是如何判断哪些是活动对象哪些是非活动对象的呢?

    对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。

    mark-sweepg 在上图中,我们把小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。

    标记清除算法作为Python的辅助垃圾收集技术主要处理的是一些容器对象,比如list、dict、tuple,instance等,因为对于字符串、数值对象是不可能造成循环引用问题。Python使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。

    3、分代回收

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

    先给出gc的逻辑:(重点)

    分配内存
    -> 发现超过阈值了
    -> 触发垃圾回收
    -> 将所有可收集对象链表放到一起
    -> 遍历, 计算有效引用计数
    -> 分成 有效引用计数=0 和 有效引用计数 > 0 两个集合
    -> 大于0的, 放入到更老一代
    -> =0的, 执行回收
    -> 回收遍历容器内的各个元素, 减掉对应元素引用计数(破掉循环引用)
    -> 执行-1的逻辑, 若发现对象引用计数=0, 触发内存回收
    -> python底层内存管理机制回收内存
    
  • 相关阅读:
    搜索框的创建
    自定义非等高 Cell
    自定义等高 Cell
    表格多选删除
    聊天布局
    表格编辑
    表格折叠
    tableView 的协议方法
    UITouch
    UIDevice
  • 原文地址:https://www.cnblogs.com/bowendown/p/12592489.html
Copyright © 2011-2022 走看看