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 底层其实有很多引用环:

  1. Exception 对象中包含 traceback 信息,其中又包含 Exception 对象本身
  2. Module-level functions 中会包含对 module 中对象的引用,其中包含该函数本身
  3. 类的实例包含对类的引用,类引用中包含对 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,通过这种分组的设计,使得垃圾回收过程速度更快,降低了垃圾回收时的暂停时间。

comments powered by Disqus