循环垃圾回收器

Python垃圾回收概述

Python中的垃圾回收机制基于引用计数(ob_refcnt),因此需要解决循环引用导致引用计数不能归零的问题。例如

# create[1]
list1=[]              # del
list2=[list1]         # del
list1.append(list2)
list3 = []            # del
list4 = []
list5 = [list3, list4] # del
list6 = [list3, list4]
list3.append(list4)
# del[2]
del list1; del list2;del list3;del list5 # [2]
# list1.ob_refcnt == list2.ob_refcnt == 1
# collect[3]
gc.collect()

虽然list1list2已经成为需要回收的垃圾,但是由于相互引用导致引用计数不能归零,从而不能触发自动回收。因此Python引入了循环垃圾收集器

循环垃圾收集器的原理

判断对象是否为垃圾的逻辑比较直白,有外部引用或者被有外部引用的对象引用的对象为非垃圾对象;否则为垃圾对象。具体过程为,遍历所有对象将对象中引用的元素(其他对象)的引用计数减一,最后引用计数不归零的对象(存在外部引用)不是垃圾对象;被不是垃圾对象引用的元素(其他对象)也不是垃圾对象;剩余的则为垃圾对象。可以归纳为如下步骤:

  1. 创建可能存在循环应用的对象时,将该对象纳入链表进行管理

  2. 遍历所有纳入管理的对象,将对象引用的元素(其他对象)的引用计数减一

  3. 再次遍历: <1> 处理对象:对该对象进行标记 所有引用计数为零的对象没有外部引用,标记为可能是垃圾; 所有引用计数不为零的对象存在外部引用,必然不是垃圾。

    |0 |可能是垃圾 |list1、list2、list3、list5| |>0 |不是垃圾 |list4、list6|

    <2> 处理对象:遍历不是垃圾对象中的元素,不是垃圾对象中的元素必然不是垃圾

  4. 最后没有被确定不是垃圾的对象就是垃圾对象

这部分处理代码比较复杂,每个对象可能作为两种角色进行处理。作为代中的对象以及作为对象中的引用元素。如果作为元素被处理,则肯定不是垃圾。

各个阶段对象中的引用计数

image

垃圾对象不一定能被自动回收

垃圾对象不一定能被自动回收。所以上面的步骤只能确定垃圾对象,然后对垃圾对象进行额外处理甄别不能回收和能被回收的部分。

  • 垃圾对象:没有外部引用的对象,也没有被有外部引用的对象引用

  • 可回收对象:垃圾对象中能够被自动回收的对象

  • reachable:非垃圾对象,存在外部引用或者被外部引用的对象引用

  • unrechable: 垃圾对象

  • collectable: 可回收对象

  • finalizers: 垃圾对象中不能被自动回收的对象。一些对象存在析构函数并且相互引用,这样的对象Python不能自动确定回收顺序,因此不能被自动回收。

不是所有对象都纳入循环垃圾收集器

一些基本对象不会产生循环引用,例如int、float、string等,所以没有必须使用循环垃圾收集器,基本的引用计数回收机制即可。还有一些容器类对象,他们中的元素都是基本元素不会引起循环引用,例如{‘a’:1}、(1, 2, 3),因此也不纳入循环垃圾收集器。所以只有部分容器类对象、生成器、含__del__类等才纳入循环垃圾收集器。

垃圾回收中的代

如上分析,整个循环垃圾收集的效率严重依赖可能引起循环引用的对象的个数。为了减少垃圾回收的动作,Python将对象分代:存活越长的对象越不可能是垃圾,就减少对其进行垃圾回收的次数。那么存活的时间长短就用经过了几次垃圾回收来判断,于是刚创建的对象为一代,当经过一次垃圾回收还存活的对象放入二代;多次一代垃圾回收后,才进行一次二代垃圾回收。Python将整个对象分为三代,当分配足够数量的对象后(700)进行一次一代回收;当进行一定数量(10)一代回收后进行二代回收;同理进行三代回收。

gcmodule.c源码分析

最后更新于

这有帮助吗?