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