Python 垃圾回收机制简介:引用计数、标记清除、分代回收

1,190 阅读3分钟

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

Python 垃圾回收机制简介:引用计数、标记清除、分代回收

Python 垃圾回收机制主要有下面三种:

  • 引用计数
  • 标记清除
  • 分代回收

引用计数

引用计数,英文是 Reference Count。在 Python 中,一个对象可以有多个引用,而每个对象中都存有指向该对象的引用总数。当某个对象的引用计数为 0 时,就可以将其回收了。可以使用 sys.getrefcount() 函数查看一个对象的引用计数。由于调用函数传参的时候,创建了临时引用,导致结果总是比期望值多 1

from sys import getrefcount

a = [1, 2, 3]
print(getrefcount(a))
"""
2
"""

b = a
print(getrefcount(b))
"""
3
"""

引用计数算法存在一个问题,就是无法处理循环引用的对象,为了解决这个问题,可以使用标记清除算法。


标记清除

标记清除,英文是 Mark-and-Sweep,它分为两个阶段:

  • 标记阶段,用于检测所有不可达的对象
  • 清除阶段,用于释放不可达对象占用的空间,以及还原可达对象的标记位

当一个对象被创建时,它的标记位被设置为 0False)。

在标记阶段,从根结点出发,也就是可以直接访问的局部变量,采用图的遍历算法,比如 DFS,将所有可达对象的标记位设置为 1True)。

在清除阶段,线性扫描堆内存,将不可达对象直接释放,将可达对象的标记位重新设置为 0,为下一次标记清除做准备。

标记清除的优点是可以处理循环引用,缺点是算法会暂停程序的执行。为了优化垃圾回收的性能,可以使用分代回收算法。


分代回收

分代回收基于弱代假设:

弱代假设,英文是 Weak Generational Hypothesis,指大多数年轻对象存活时间较短,而老对象倾向于存活较长时间。

Python 将所有对象分为 012 三代,并用三个链表将它们串联起来,所有的新建对象都是 0 代对象。

当分配对象 Object Allocation 和释放对象 Object Deallocation 两者的差值达到 700 的时候,就会启动垃圾回收。每次垃圾回收都会遍历 0 代链表,如果对象存活,则升级为下一代对象。每 100 代垃圾回收,会配合一次 1 代垃圾回收;每 101 代垃圾回收,会配合一次 2 代垃圾回收。

import gc
gc.get_threshold()

这样可以得到一个三元组 (700, 10, 10)。另外,可以使用 gc.set_threshold(a, b, c) 自定义阈值。


gc 模块常用函数

  • gc.collect(generation):执行垃圾回收。0 表示只回收 0 代对象,1 表示回收 0 代和 1 代对象,2 表示回收 012 代对象。默认为 2
  • gc.get_threshold():返回垃圾回收的阈值,是一个三元组,默认为 (700, 10, 10)
  • gc.set_threshold(a, b, c):设置垃圾回收的阈值。
  • gc.get_count():返回垃圾回收的计数器,是一个三元组。

参考

python的垃圾回收是采用的引用计数算法,而且在引用计数的基础上辅以标记-清除和分代回收算法。以引用计数算法来跟踪和回收垃圾;以标记-清除来解决对象产生循环引用造成无法回收的问题;以分代回收以空间换时间来进一步提高垃圾回收!