除了通过引用计数直接销毁对象之外,python
还是拥有内在GC
机制的,并且也有完整的一套流程。
如果只有通过引用计数销毁对象这种机制,那么随便构造一个循环引用就会造成内存泄漏,比如下面的代码:
1 | def _dump_gc(): |
打印出来的结果是:
1 | 13707 |
很显然,当退出_test_internal
作用域时,gc
对象的数量没有变化,这就说明在_test_internal
里创建的a、b
两个对象没有被立即释放掉。如果注释掉两个append
行,就能看到打印结果第三行变成了13707
,说明a、b
在退出函数时就被销毁了。
所以首先,我们从循环引用入手,来研究一下python
的gc
机制(好巧不巧的是,python
的gc
也是专门为循环引用而设置的)。调用gc.collect
触发gc
之后,会跑到gc_collect_main
触发完整的gc
流程。gc_collect_main
是python
整个gc
流程的主入口,我们来看其中的代码:
1 | // gcmodule.c |
gc_collect_main
总共执行了以下几个步骤,一个一个来说。
首先更新指定generation
后的分配计数(+1),指定generation
及以下的全部归0。这是因为python
也自带了分代回收机制,gc.collect
入参是generation
,指定generation
以下的全部都会被gc
掉。
python
的gc
分代总共是3代,而gc.collect
默认值是2,也表示最高一代,通俗点讲就是Full GC
。每一代的对象数目如果超过特定值,就会触发自动gc。
然后做的一个事情是,将指定generation
前代的和当代的所有GC_Head
全部串到一个链表上,这样只需要处理这一个链表就能回收所有东西。每个python
对象都会自带GC_Head
,串到特定genenration
的链表中,用于在GC时候被识别到。
之后做的很关键的一步是deduce_unreachable
,其作用是模拟去引用的流程,探测无法从gc
根对象直接达到的对象,放到单独的unreachable
列表中。并且在这一步会通过遍历对象之间的引用关系并-1引用的方式,从而将对象之间的循环引用暂时归零。deduce_unreachable
的代码如下:
1 | static inline void |
好比一个场景:总共有4个list
对象,分别为l1
到l4
,其中l1
、l2
循环引用,l3
、l4
循环引用,l1
和l2
与l3
跟l4
之间没有关联。之后若再有一个变量a
引用了l1
,那么经过deduce_unreachable
之后,会呈现如下的结果:
- 在
subtract_refs
步骤中,l1
的引用数目变成1,l2
、l3
、l4
变成0 - 在
move_unreachable
步骤中,发现l1
引用数目为1,即将l1
以及其引用到的所有变量标记为reachable
- 最后剩下来引用数仍然为0的
l3
、l4
,放到unreachable
链表中
- 最后剩下来引用数仍然为0的
经历deduce_unreachable
步骤存活下来的reachable
对象,会直接被移动到下一个generation
。接下来只需要考虑对unreachable
的对象进行销毁了。
接下来的操作是将unreachable
链表中,会处理一些C
层类型定义里含有旧版tc_del
方法的类型的对象,这些对象及其直接或间接引用的对象全部都会被移动到单独的finalizers
链表中,而这个链表中的对象会被单独维护,无法被回收。
在以前的版本中,如果python
类型定义了__del__
方法,那么这些类型的对象就会移动到finalizers
链表中。直到PEP442之后,__del__
方法就对应了C
层类型定义的另外一个tc_finaliz
e方法了,因此包含__del__
方法的类型的对象,不一定会移动到finalizers
链表中,而是会在后面的步骤中触发tc_finalize
逻辑。
再之后,针对剩下的unreachable
对象,通过handle_weakrefs
方法解除其它对象对其的弱引用。然后调用finalize_garbage
销毁unreachable
对象。finalize_garbage
的代码如下:
1 | static void |
finalize_garbage
实质是调用对象类型定义的tp_finalize
方法析构对应的对象,并减少其引用为0从而释放对象内存。由于某些对象类型可能没有默认的tp_finalize
方法,因此经过这一步骤之后,还会存留一些未销毁的对象。
针对未销毁的对象,之后会通过handle_resurrected_objects
进行处理。在handle_resurrected_objects
中会再一次执行deduce_unreachable
模拟去引用操作,存活下来的unreachable
对象就被移到下一个generation
,而剩下的对象就会被移动到final_unreachable
链表进行后面的销毁操作。
通过delete_garbage
方法会对final_unreachable
链表的对象进行销毁,其代码如下:
1 | static void |
delete_garbage
中,每一个对象都会调用其类型的tp_clear
方法,减少对象引用数目为0,触发对象的销毁。
在delete_garbage
之后,终于就会对先前单独拎出来的finalizers
链表进行处理。finalizers
链表中所有的内容都会通过handle_legacy_finalizers
方法被移动到当前gcstate
的garbage
链表中单独维护,不会被销毁。
之后进行一些数据清理和数据统计逻辑,整个gc
流程就完成了。