因应上一篇提到的问题,Cpython还有两种回收机制:标记与清除算法与分代机制。
标记与清除算法
首先,Cpython会创建一个纪录可能需要回收物件的linked list,当物件被创造时,
Cpython会判断该物件的类型是否开启GC功能,假如有GC功能,则放入该linked list。
PyTypeObject PyList_Type = {
...
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LIST_SUBCLASS |
_Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_SEQUENCE, /* tp_flags */
...
};
我们可以发现list有Py_TPFLAGS_HAVE_GC,所以当我们创建list类型的物件,此物件就会
被放入list。
另外,Cpython会在该物件上加上PyGC_Head,PyGC_Head包含两个指标:
prev指向前一个须追踪物件,next指向下一个须追踪物件
透过两个指标,Cpython创造一个linked list追踪可能需要回收的物件。
回收机制标记了可能需回收的物件,之后,它将物件区分为两类:
可达(reachable)与不可达(unreachable)
可达是指有变量引用或有可达物件引用;不可达则无。
区分可达与不可达后,清除就是回收不可达物件。
分代机制
我们有了标记与清除算法,我们可以设定每过一段时间就清除一次,但,每清除一次就
必须遍历整个linked list,linked list里面可能有一些长时间使用的东西,每次遍历都
会扫到很多不需要清理的物件,所以,Cpython设计了分代机制。
分代机制就是将物件列表分成三代:
第零代:新物件被创造就放在这里
第一代:第零代被清理后,活下来的物件移到这里
第二代:第一代被清理后,活下来的物件移到这里
再来讨论何时回收这三代的物件
head前面我们提过了,每代还有threshold跟count
threshold是每一代的阀值,count每代的意义不同,在第零代,count代表第零代列表的
物件数量,每次创建可能需回收物件,count += 1。
第一代count代表第零代的垃圾回收次数,第二代的Count代表第一代的垃圾回收次数
当count > threshold,该代就会执行垃圾回收。
每代的可能需要遍历的最大数量如下:
第零代:generation[0].threshold
第一代:generation[0].threshold * generation[0].threshold(第零代全部存活)
第二代:无上限
所以,第二代可能囤积大量物件,所以,为了减少回收第二代的次数
Cpython对第二代回收加了个条件:
当第二代中等待第一次完全回收的物件数量/上次完全回收存活下来的物件数量 > 1/4
第二代才会执行垃圾回收
GC linked list
关于此linked list如何新增与删除新物件的问题
其实它就跟一般linked list差不多 不赘述
参考资料:
https://reurl.cc/jQMZd1