python-源码阅读(6)-python垃圾回收

引用计数与垃圾收集

引用计数机制所带来的维护引用计数的额外操作与python运行中所进行的内存分配和释放, 引用赋值次数是成正比的, 这也是引用计数执行效率较慢的原因(软肋).为了与引用计数机制搭配, 在内存的分配和释放上获得最高的效率, python因此设计了很多内存池机制, 如上一篇的小块内存池. 而且在一些PyObject中如PyLongObject, PyUnicodeObject, PyDictObject, PyListObject等都有与各个对象相关的内存池机制. 这些大量使用的面向特定对象内存池机制正式为了弥补引用计数的软肋.

引用计数还需要处理一个致命的弱点, 就是循环引用. 当若干对象相互引用, 会造成每一个对象的引用计数都不会0, 因此这些对象所占用的内存永远不会回收.

python中的标记-清除

对与循环应用问题,python采用标记-清除。

  1. container对象之间如list, dict, class等. 当python的垃圾收集机制运行时, 只需要检测这些container对象,这些container对象包含一个双向链表
  2. python 通过ref_count计算应用计数垃圾回收,gc_ref是ref_count副本,用于循环应用的垃圾回收,GC时分两步,一是gc_ref自减1,摘除循环引用;二是从root根出发标记是否可达。最后gc_ref为0且不可达将被回收

分代回收

分代回收是基于这样的一个统计事实,对于程序,存在一定比例的内存块的生存周期比较短;而剩下的内存块,生存周期会比较长,甚至会从程序开始一直持续到程序结束。生存期较短对象的比例通常在 80%~90% 之间,这种思想简单点说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集。这样在执行标记-清除算法时可以有效减小遍历的对象数,从而提高垃圾回收的速度。

python gc给对象定义了三种世代(0,1,2),每一个新生对象在generation zero中,如果它在一轮gc扫描中活了下来,那么它将被移至generation one,在那里他将较少的被扫描,如果它又活过了一轮gc,它又将被移至generation two,在那里它被扫描的次数将会更少。

总结

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