Python垃圾回收浅析
https://docs.python.org/3.10/library/gc.html
https://github.com/python/cpython/blob/main/InternalDocs/garbage_collector.md
Garbage Collector#
CPython 主要的垃圾回收算法是使用引用计数,当一个对象的引用计数为 0 时会将其回收,同时将其引用的其它对象的引用计数减 1。
引用计数算法的主要问题是其无法应对循环引用,下面代码中构建出一个数组,并将第一个元素设置为其自身:
>>> container = []
>>> container.append(container)
>>> sys.getrefcount(container)
3
>>> del container
此时该数组的引用计数永远无法降为 0,因为它引用了它自己。
因此在 Python 中还有另外一个机制来回收这些循环引用的对象,当整个引用环没有地方再引用时,将环中的所有对象回收。
这个机制就叫做 Garbage Collector (GC),这个名字跟 garbage collection 有点混淆,引用计数也是 garbage collection 的一种。
为了实现 GC,Python 底层在每个对象上增加了额外的字段,可以将所有的对象形成一个双向链表,感兴趣的可以看具体的文档说明。
值得注意的是尽管我们可能觉得自己写的代码中循环引用不多,但在 Python 底层其实有很多引用环:
- Exception 对象中包含 traceback 信息,其中又包含 Exception 对象本身
- Module-level functions 中会包含对 module 中对象的引用,其中包含该函数本身
- 类的实例包含对类的引用,类引用中包含对 module 的引用,module 中又包含其全部内容的引用,包括类实例本身
Incremental collection#
在进行 GC 时整个程序会暂停,如果每次 GC 都对系统内所有的对象进行扫描,暂停时间将会很长。
为了降低 GC 的暂停时间,Python 将对象分成 3 组进行垃圾回收。
这个设计基于一个假设:大多数对象的生命周期都很短。这个假设已经被证明适用于大多数的 Python 程序。
简单来说,系统内有 3 个对象集合,新生成的对象保存在 generation 0 集合中,当对象在一次垃圾回收中生存下来,会被移到 generation 1 集合中,当对 generation 1 集合进行垃圾回收时存活的对象会被移到 generation 2 集合中。
系统对 generation 0 集合进行垃圾回收的频率要高于 generation 1 和 generation 2,通过这种分组的设计,使得垃圾回收过程速度更快,降低了垃圾回收时的暂停时间。