• python垃圾回收杂谈


    当创建对象时Python立即向操作系统请求内存。每当对象的引用数减为0,Python垃圾回收器立刻挺身而出,立即将其释放,把内存还给操作系统。
    在Python中,每个对象都保存了一个称为引用计数的整数值,来追踪到底有多少引用指向了这个对象。无论何时,如果我们程序中的一个变量或其他对象引用了目标对象,Python将会增加这个计数值,而当程序停止使用这个对象,则Python会减少这个计数值。一旦计数值被减到零,Python将会释放这个对象以及回收相关内存空间。而对于创建的对象,无论存在循环引用与否,只有还在使用,没有被释放,就会慢慢的随着分代的策略,慢慢进入2代(老年代)

    通过频繁的处理零代链表中的新对象,Python的垃圾收集器将把时间花在更有意义的地方:它处理那些很快就可能变成垃圾的新对象。同时只在很少的时候,当满足阈值的条件,收集器才会去处理那些老变量。这种算法的根源来自于弱代假说(weak generational hypothesis):这个假说由两个观点构成:首先是年轻的对象通常死的也快,而年老对象则很有可能存活更长的时间。


    引用计查看方法:sys.getrefcount?,在ipython里使用问号查看帮助信息

    In [17]: sys.getrecursionlimit?
    Docstring:
    getrecursionlimit()

    Return the current value of the recursion limit, the maximum depth
    of the Python interpreter stack.  This limit prevents infinite
    recursion from causing an overflow of the C stack and crashing Python.
    Type:      builtin_function_or_method

    In [18]:


    hex(id(a))查看是否真的被回收

    感觉 标记-清除 与 分代可以看成一个整体
    标记-清除过程先从零代开始,清理最频繁的也是零代,然后是第一代、第二代


    Python中的循环引用总是发生在container对象之间, 所谓containser对象即是内部可持有对其他对象的引用: list/dict/class/instance等等,不会是int str
    "标记-清除"是为了解决循环引用的问题.可以包含其他对象引用的容器对象(比如:list,set,dict,class,instance)都可能产生循环引用


    新创建的对象都在0代,0代不被引用计数和标记清除算法回收的对象会被放入1代,1代不被引用计数和标记清除算法回收的对象会被放入2代


    gc.get_threshold
    gc.set_threshold正确理解,默认值[700,10,10]
    0代的700表示新创建的对象超过700个则对0代进行垃圾回收,能回收的回收掉,不能回收的放到1代中。1代的默认阈值10表示记录0代进行回收的次数,即0代回收次数超过10次,则对1代进行垃圾回收,能回收的回收,不能回收的放入2代;2代的10表示1代回收的次数阈值,当1代回收次数超过10次时,对2代进行回收,能回收的回收,不能回收的继续放到2代里,等待下一次回收。每一代回收完,就会变化值0

    gc.collect()对所有代进行回收,回收完,恢复gc.get_count()的次数(0,0,0)
    gc.get_count()返回0代创建对象的个数即当前已经发生回收的次数,比如(10,9,5)表示当前新建的对象10个,还没有经过分代回收,而已经发生回收9*5=45次

    python2没有这个函数gc.get_stats()返回0、1、2代各自的(还未回收的数量,已经进行垃圾回收的次数,不可以回收的对象)
    In [16]: gc.get_stats()
    Out[16]:
    [{'collected': 16564, 'collections': 333, 'uncollectable': 0},
     {'collected': 2373, 'collections': 27, 'uncollectable': 0},
     {'collected': 119, 'collections': 10, 'uncollectable': 0}]


    执行一次gc.collect(0),则gc.get_count()的第2个值即1代的值加1,表示进行了一次0代回收
    执行一次gc.collect(1),则gc.get_count()的第3个值即2代的值加1,同时第2个值即1代的值变为0,表示进行了一次1代回收
    执行一次gc.collect(2),则gc.get_count()的第3个值即2代的值变为0,表示进行了一次2代回收


    小对象:int、str并不会创建对象,所有使用list,dict、tuple等;str/int有个地址池,这类小对象的创建不体现在计数里,所以gc.get_count()[0]里体现不出变化
    验证的时候使用python3.7的编译环境,不要使用ipython这种环境

    gc.set_threshold正确理解,默认值[700,10,10]:7000*10*10=70000

    开启debug和gc.enable():gc.set_debug(gc.DEBUG_STATS|gc.DEBUG_COLLECTABLE)

    示例:
    执行下面的的语句,创建10W个对象,你会发现有100+次0代垃圾回收,10+次1代回收,1次2代垃圾回收,证明:是根据频次来计算的
    以python3测试为主:
    python里的批量创建对象的方法:locals()

    for i in range(100000):
      locals()['str'+str(i)]={'a':i}

    或者稍微大点的对象:
    for i in range(100000):
      locals()['strwo'+str(i)]={'a':{'b':str(i)},'xx':{'wo':str(i)+'0代的700表示新创建的对象超过700则对0代进行垃圾回收,能回收的回收   ,不能回收的放到1代中。1代的默认阈值10表示记录0代进行回收的次数,即0代回收次数超过10次,则对1代进'}}

    参考:
    https://foofish.net/python-gc.html
    https://juejin.im/post/5b34b117f265da59a50b2fbe

  • 相关阅读:
    在做nginx的服务器http错误和解决办法
    利用ffmpeg将MP4文件切成ts和m3u8
    整理:服务器命令(笔记)
    函数式编程
    Object
    promise
    前端模块化、工程化
    函数
    restful && rpc
    全局、局部变量
  • 原文地址:https://www.cnblogs.com/shengulong/p/10144866.html
Copyright © 2020-2023  润新知