zoukankan      html  css  js  c++  java
  • 面试题-python 垃圾回收机制?

    前言

    简历上写着熟悉 python 面试官上来就问:说下python 垃圾回收机制?一盆冷水泼过来,瞬间感觉 python 不香了。
    Python中,主要通过引用计数(Reference Counting)进行垃圾回收。

    引用计数

    在Python中每一个对象的核心就是一个结构体PyObject,它的内部有一个引用计数器(ob_refcnt)。
    程序在运行的过程中会实时的更新 ob_refcnt 的值,来反映引用当前对象的名称数量。

    当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它被垃圾回收。
    但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。

    sys.getrefcount() 可以查看对象的引用次数,先自己先有个class 创建一个对象,此时引用次数是1,由于 sys.getrefcount() 也会引用一次,所以看到的会在引用次数基础上+1

    # 作者-上海悠悠 QQ交流群:717225969
    # blog地址 https://www.cnblogs.com/yoyoketang/
    import sys
    
    
    class MyObject():
        def __init__(self):
            self.x = 1
    
    a = MyObject()               # 创建一个对象
    print("MyObject 引用次数:", sys.getrefcount(a))    # 查看引用次数
    

    运行结果:MyObject 引用次数: 2

    导致引用计数 +1 的情况

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

    导致引用计数-1 的情况

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

    对象销毁

    下面代码a增加一次引用,赋值给a后,b和a都是指向同一个对象,当我们不用的时候就可以用del 销毁对象a和b

    # 作者-上海悠悠 QQ交流群:717225969
    # blog地址 https://www.cnblogs.com/yoyoketang/
    
    import sys
    
    
    class MyObject():
        def __init__(self):
            self.x = 1
    
    a = MyObject()               # 创建一个对象
    print("MyObject 引用次数:", sys.getrefcount(a))    # 查看引用次数
    
    
    # a增加一次引用
    b = a
    print("MyObject 引用次数:", sys.getrefcount(a))    # 查看引用次数
    
    # 销毁对象b
    del b
    print("MyObject 引用次数:", sys.getrefcount(a))    # 查看引用次数
    
    # 销毁对象a
    del a
    

    对象作为参数,传到函数里面也会被引用一次,看下面这个案例

    import sys
    
    a = [1, 2, 3]
    print(sys.getrefcount(a))  # 2次
    b = a
    print(sys.getrefcount(a))  # 3次
    c = b
    d = b
    e = c
    f = e
    g = d
    print(sys.getrefcount(a))  # 8次
    

    输出结果

    2
    3
    8
    

    a、b、c、d、e、f、g 这些变量全部指代的是同一个对象,而 sys.getrefcount() 函数并不是统计一个指针,而是要统计一个对象被引用的次数,所以最后一共会有 8 次引用。
    如果我们一个个去销毁对象,很显然会浪费时间,于是可以用gc来垃圾回收了,gc.collect() 即可手动启动垃圾回收

    # 作者-上海悠悠 QQ交流群:717225969
    # blog地址 https://www.cnblogs.com/yoyoketang/
    import sys
    import gc
    
    a = [1, 2, 3]
    print(sys.getrefcount(a))  # 2次
    b = a
    print(sys.getrefcount(a))  # 3次
    c = b
    d = b
    e = c
    f = e
    g = d
    print(sys.getrefcount(a))  # 8次
    
    del a
    gc.collect()  # 垃圾回收
    

    循环引用

    当a对象引用b,b对象也引用a,两个互相引用的时候,互相引用导致它们的引用数都不为 0。
    初始化的时候,会生成一个大的列表[i for i in range(100000)],导致占用很大的内存

    # 作者-上海悠悠 QQ交流群:717225969
    # blog地址 https://www.cnblogs.com/yoyoketang/
    import sys
    
    
    class MyObject():
        def __init__(self):
            self.y = [i for i in range(1000000)]
            self.x = 1
    
    
    while True:
        a = MyObject()               # 创建一个对象
        b = MyObject()
        print("MyObject 引用次数a:", sys.getrefcount(a))    # 查看引用次数
        print("MyObject 引用次数b:", sys.getrefcount(b))    # 查看引用次数
        a.x = b     # a的x属性赋值b
        b.x = a     # b的x属性赋值a
        # 销毁对象a和b
        del a
        del b
    

    运行一段时间后,可以观察内存的变化,会一直增加不释放

    使用 gc.collect() 垃圾回收

    # 作者-上海悠悠 QQ交流群:717225969
    # blog地址 https://www.cnblogs.com/yoyoketang/
    import sys
    import gc
    
    
    class MyObject():
        def __init__(self):
            self.y = [i for i in range(1000000)]
            self.x = 1
    
    
    while True:
        a = MyObject()               # 创建一个对象
        b = MyObject()
        print("MyObject 引用次数a:", sys.getrefcount(a))    # 查看引用次数
        print("MyObject 引用次数b:", sys.getrefcount(b))    # 查看引用次数
        a.x = b     # a的x属性赋值b
        b.x = a     # b的x属性赋值a
        # 销毁对象a和b
        del a
        del b
        gc.collect()
    

    再次运行,内存就得到释放了

    在Python中,主要通过引用计数进行垃圾回收;通过 “标记-清除” 解决容器对象可能产生的循环引用问题;通过 “分代回收” 以空间换时间的方法提高垃圾回收效率。

    参考资料http://c.biancheng.net/view/5540.html
    参考资料https://www.cnblogs.com/donghe123/p/13275183.html
    参考资料https://testerhome.com/topics/16556

  • 相关阅读:
    教会他人,让其成为你的接棒人
    2015年看的52部电影计划
    我的2015年读书计划,每两周读完一本书!
    拯救你的电脑之文件命名规范与目录规划
    出租WiFi到底靠不靠谱?
    使用观察者模式更新Fragment的内容
    android静默安装和智能安装(转)
    Android拨打电话不弹出系统拨号界面总结
    Android通过AIDL和反射调用系统拨打电话和挂断电话
    Android设为系统默认的短信应用
  • 原文地址:https://www.cnblogs.com/yoyoketang/p/14485480.html
Copyright © 2011-2022 走看看