美文网首页
Python基础30-面向对象(内存管理机制-引用计数/垃圾回收

Python基础30-面向对象(内存管理机制-引用计数/垃圾回收

作者: Jacob_LJ | 来源:发表于2018-05-24 23:31 被阅读211次

    Python基础-面向对象(内存管理机制)

    1 对象存储

    1. 在Python中万物皆对象
    不存在基本数据类型,`0,  1.2,  True, False, "abc"`等,这些全都是对象
    
    1. 所有对象, 都会在内存中开辟一块空间进行存储
    2.1 会根据不同的类型以及内容, 开辟不同的空间大小进行存储
    2.2 返回该空间的地址给外界接收(称为"引用"), 用于后续对这个对象的操作
    2.3 可通过 id() 函数获取内存地址(10进制)
    2.4 通过 hex() 函数可以查看对应的16进制地址
    
    class Person:
        pass
    
    p = Person()
    print(p)
    print(id(p))
    print(hex(id(p)))
    
    >>>> 打印结果
    
    <__main__.Person object at 0x107030470>
    4412605552
    0x107030470
    
    1. 对于整数和短小的字符, Python会进行缓存; 不会创建多个相同对象
    此时, 被多次赋值, 只会有多份引用
    
    num1 = 2
    num2 = 2
    print(id(num1), id(num2))
    
    >>>> 打印结果
    
    4366584464 4366584464
    
    1. 容器对象, 存储的其他对象, 仅仅是其他对象的引用, 并不是其他对象本身
    4.1 比如字典, 列表, 元组这些"容器对象"
    4.2 全局变量是由一个大字典进行引用
    4.3 可通过 global() 查看
    

    2 对象回收

    2.1 引用计数器

    2.1.1概念

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

    2.1.2 计数器变化常见场景

    • 引用计数+1场景
    1、对象被创建
        p1 = Person()
    2、对象被引用
        p2 = p1
    3、对象被作为参数,传入到一个函数中
        log(p1)
        这里注意会+2, 因为内部有两个属性引用着这个参数
    4、对象作为一个元素,存储在容器中
        l = [p1]
    
    • +1 情况3说明:内部有两个属性引用着这个参数
      Python2.x 下打印 :_globals__func_globals 引用该参数对象,计数+2
      Python3.x 下打印:则只有一个_globals__引用该对象,同样计数+2
    import sys
    
    class Person:
        pass
    
    p_xxx = Person() # 1
    
    print(sys.getrefcount(p_xxx))
    
    
    def log(obj):
        print(sys.getrefcount(obj))
    
    log(p_xxx)
    
    
    # for attr in dir(log):
    #     print(attr, getattr(log, attr))
    
    
    >>>> 打印结果
    
    2
    4
    
    • 引用计数-1场景
    1、对象的别名被显式销毁
        del p1
    2、对象的别名被赋予新的对象
        p1 = 123
    3、一个对象离开它的作用域
        一个函数执行完毕时
        内部的局部变量关联的对象, 它的引用计数就会-1
    4、对象所在的容器被销毁,或从容器中删除对象
    

    2.1.3 查看引用计数

    • 注意计数器会>1,因为对象在 getrefcount方法中被引用
    import sys
    sys.getrefcount(对象) 
    
    import sys
    
    class Person:
        pass
    
    p1 = Person() # 1
    
    print(sys.getrefcount(p1)) # 2
    
    p2 = p1 # 2
    
    print(sys.getrefcount(p1)) # 3
    
    del p2 # 1
    print(sys.getrefcount(p1)) # 2
    
    del p1
    # print(sys.getrefcount(p1)) #error,因为上一行代码执行类p1对象已经销毁
    
    >>>> 打印结果
    
    2
    3
    2
    

    2.2 循环引用

    • 对象间互相引用,导致对象不能通过引用计数器进行销毁
    # 循环引用
    class Person:
        pass
    
    class Dog:
        pass
    
    p = Person() 
    d = Dog()   
    
    p.pet = d 
    d.master = p
    
    • 使用 objgraph 模块
    • objgraph.count() 可以查看, 垃圾回收器, 跟踪的对象个数
    # 正常情况
    import objgraph
    
    class Person:
        pass
    
    
    class Dog:
        pass
    
    p = Person()
    d = Dog()
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    # 删除 p, d之后, 对应的对象是否被释放掉
    del p
    del d
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    >>>> 打印结果
    
    1
    1
    0
    0
    
    
    # 循环引用
    import objgraph
    
    class Person:
        pass
    
    
    class Dog:
        pass
    
    p = Person()
    d = Dog()
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    p.pet = d
    d.master = p
    
    # 删除 p, d之后, 对应的对象是否被释放掉
    del p
    del d
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    >>>> 打印结果
    
    1
    1
    1
    1
    

    2.2 垃圾回收

    2.2.1 主要作用

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

    2.2.2 垃圾回收底层机制

    2.2.2.1 如何找到 循环引用 对象

    1. 收集所有的"容器对象", 通过一个双向链表进行引用
    * 容器对象
        可以引用其他对象的对象
            列表
            元组
            字典
            自定义类对象
            ...
    * 非容器对象
        不能引用其他对象的对象
            数值
            字符串
            布尔
            ...
        注意: 针对于这些非容器对象的内存, 有其他的管理机制
    
    1. 针对于每一个"容器对象", 通过一个变量gc_refs来记录当前对应的引用计数
    2. 对于每个"容器对象",找到它引用的"容器对象", 并将这个"容器对象"的引用计数 -1
    3. 经过步骤3之后, 如果一个"容器对象"的引用计数为0, 就代表这个对象可以被回收, 而它肯定是因为"循环引用"导致不能被回收(存活到现在)

    2.2.2.2 如何提升查找"循环引用"的性能?

    1 问题:

    • 如果程序当中创建了很多个对象, 而针对于每一个对象都要参与"检测"过程; 则会非常的耗费性能

    2 假设:

    • 基于这个问题, 产生了一种假设:
    1. 越命大的对象, 越长寿
    2. 假设一个对象10次检测都没给它干掉, 就认定这个对象一定很长寿, 就减少这货的"检测频率"
    

    3 设计机制:

    • 分待回收 机制
    1. 默认一个对象被创建出来后, 属于 0 代
    2. 如果经历过这一代"垃圾回收"后, 依然存活, 则划分到下一代
    3. "垃圾回收"的周期顺序为
        0代"垃圾回收"一定次数, 会触发 0代和1代回收
        1代"垃圾回收"一定次数, 会触发0代, 1代和2代回收
    

    2.2.2.3 垃圾检测时机

    • 垃圾回收器当中, 新增的对象个数-消亡的对象个数 , 达到一定的阈值时, 才会触发, 垃圾检测
    • 通过 gc 模块查看当前垃圾回收机制促发条件及设置条件
    import gc
    
    # 获取
    print(gc.get_threshold())
    
    >>>> 打印结果
    (700, 10, 10) 
    # 参数1,700;代表:新增的对象个数-消亡的对象个数 == 700 时会促发垃圾检测时机
    # 参数2,10;代表:当第0代对象检测次数达到10次时候,会促发0代和1代对象的检测
    # 参数3,10;代表:当第1代对象检测次数达到10次时候,会促发0代、1代和2代对象的检测
    
    
    # 设置
    gc.set_threshold(200, 5, 5)
    print(gc.get_threshold())
    
    >>>> 打印结果
    (200, 5, 5)
    
    

    2.2.3 垃圾回收时机

    2.2.3.1 自动回收

    1. 触发条件
    * 开启垃圾回收机制
    * 且达到启动垃圾回收对应的阈值
    
    1. 开启垃圾回收机制
    gc.enable()
        开启垃圾回收机制(默认开启)
    gc.disable()
        关闭垃圾回收机制
    gc.isenabled()
        判定是否开启
    
    1. 启动垃圾回收对应的阈值
    • 垃圾回收器中, 新增的对象个数和释放的对象个数之差到达某个阈值
    查看方法
        gc.get_threshold()
            获取自动回收阈值
        gc.set_threshold()
            设置自动回收阈值
    
    
    # 自动回收
    import gc
    
    gc.disable()
    print(gc.isenabled())
    
    gc.enable()
    
    print(gc.isenabled())
    
    print(gc.get_threshold())
    gc.set_threshold(1000, 15, 5)
    

    一般会将对应的阈值设置成更大的值,这样可以提高程序性能

    2.2.3.2 手动回收

    1. 触发条件
    • 调用 gc 模块的collection() 方法
    gc.collect(generation=None)
    """
        collect([generation]) -> n
        
        With no arguments, run a full collection.  The optional argument
        may be an integer specifying which generation to collect.  A ValueError
        is raised if the generation number is invalid.
        
        The number of unreachable objects is returned.
        """
    
    • generation 参数:如果为空时,则表明全代回收;指定代数的话,则会检测该代之前的所有对象,如:generation = 2,则检测0、1和2代的对象
    • 不管之前垃圾回收机制是否开启,调用 collection() 方法后都会执行一次垃圾回收
    1. 通过 objgraph 查看该类实例引用计数
    import objgraph
    class Person:
        pass
    
    class Dog:
        pass
    
    p = Person()
    d = Dog()
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    >>>> 打印结果
    0
    0
    
    1. 因循环引用情况,引用计数器处理不了
    import objgraph
    
    class Person:
        pass
    
    class Dog:
        pass
    
    p = Person()
    d = Dog()
    
    p.pet = d
    d.master = p
    
    del p
    del d
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    >>>> 打印结果
    1
    1
    
    1. 手动促发垃圾回收,处理循环引用对象
    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"))
    print(objgraph.count("Dog"))
    
    >>>> 打印结果
    0
    0
    
    1. 即使将垃圾回收机制关闭,调用gc.collect() 时候都会启动触发垃圾回收,执行完后并未自动开启
    import objgraph
    import gc
    
    gc.disable() #手动关闭垃圾回收
    
    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"))
    print(objgraph.count("Dog"))
    
    print(gc.isenabled()) # 检查关闭后是否自动开启
    
    >>>> 打印结果
    0
    0
    False
    

    3 循环引用 - 版本兼容方案、弱引用打破循环引用

    1. 循环引用通过手动启动垃圾回收机制进行回收
    import objgraph
    import gc
    
    # 1. 定义了两个类
    class Person:
        pass
    
    class Dog:
        pass
    
    # 2. 根据这两个类, 创建出两个实例对象
    p = Person()
    d = Dog()
    
    # 3. 让两个实例对象之间互相引用, 造成循环引用
    p.pet = d
    d.master = p
    
    # 4. 尝试删除可到达引用之后, 测试真实对象是否有被回收
    del p
    del d
    
    # 5. 通过"引用计数机制"无法回收; 需要借助"垃圾回收机制"进行回收
    gc.collect()
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    >>>> 打印结果
    0
    0
    
    1. 可以通过重写类方法 __del__ 查看类实例被释放时标识
    • Python3.x 环境
    import objgraph
    import gc
    
    class Person:
        def __del__(self):
            print("Person对象, 被释放了")
        pass
    
    class Dog:
        def __del__(self):
            print("Dog对象, 被释放了")
        pass
    
    p = Person()
    d = Dog()
    
    p.pet = d
    d.master = p
    
    del p
    del d
    
    gc.collect()
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    >>>> 打印结果
    Person对象, 被释放了
    Dog对象, 被释放了
    0
    0
    
    • Python2.x 环境时:
      如果循环引用对象任意一个类里面实现了__del__方法,都会导致垃圾回收机制无法正常运行
      并且不会执行__del__方法
    import objgraph
    import gc
    
    class Person:
        def __del__(self):
            print("Person对象, 被释放了")
        pass
    
    class Dog:
        def __del__(self):
            print("Dog对象, 被释放了")
        pass
    
    p = Person()
    d = Dog()
    
    p.pet = d
    d.master = p
    
    del p
    del d
    
    gc.collect()
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    >>>> 打印结果
    1
    1
    
    • Python2.x 中循环引用类都没有重写__del__时,则垃圾回收机制正常
    import objgraph
    import gc
    
    class Person:
        # def __del__(self):
        #     print("Person对象, 被释放了")
        pass
    
    class Dog:
        # def __del__(self):
        #     print("Dog对象, 被释放了")
        pass
    
    p = Person()
    d = Dog()
    
    p.pet = d
    d.master = p
    
    del p
    del d
    
    gc.collect()
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    >>>> 打印结果
    0
    0
    

    4 打破循环引用的方法

    1. 手动强制将循环引用的属性置 None。 p.pet = None
      Python2.x 环境
    import objgraph
    import gc
    
    # 1. 定义了两个类
    class Person:
        def __del__(self):
            print("Person对象, 被释放了")
        pass
    
    class Dog:
        def __del__(self):
            print("Dog对象, 被释放了")
        pass
    
    p = Person()
    d = Dog()
    
    p.pet = d
    d.master = p
    
    p.pet = None #强制置 None
    del p
    del d
    
    gc.collect()
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    >>>> 打印结果
    Dog对象, 被释放了
    Person对象, 被释放了
    0
    0
    
    
    1. 通过弱引用方式打破循环引用
      Python2.x 环境
    • 导入 weakref 模块
    • 一对一弱引用 d.master = weakref.ref(p)
    
    import objgraph
    import gc
    import weakref
    
    # 1. 定义了两个类
    class Person:
        def __del__(self):
            print("Person对象, 被释放了")
        pass
    
    class Dog:
        def __del__(self):
            print("Dog对象, 被释放了")
        pass
    
    p = Person()
    d = Dog()
    
    p.pet = d
    d.master = weakref.ref(p)
    
    del p
    del d
    
    gc.collect()
    
    print(objgraph.count("Person"))
    print(objgraph.count("Dog"))
    
    >>>> 打印结果
    
    Person对象, 被释放了
    Dog对象, 被释放了
    0
    0
    
    • 弱引用一对多
    # 单个写法
    p.pets = {"dog":  weakref.ref(d1), "cat":  weakref.ref(c1)}
    或
    字典形式
    p.pets = weakref.WeakValueDictionary({"dog": d1, "cat": c1})
    
    一般还有 如下方法
    WeakKeyDictionary
    WeakSet
    ···

    相关文章

      网友评论

          本文标题:Python基础30-面向对象(内存管理机制-引用计数/垃圾回收

          本文链接:https://www.haomeiwen.com/subject/jrsejftx.html