这是我参与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,它分为两个阶段:
- 标记阶段,用于检测所有不可达的对象
- 清除阶段,用于释放不可达对象占用的空间,以及还原可达对象的标记位
当一个对象被创建时,它的标记位被设置为 0(False)。
在标记阶段,从根结点出发,也就是可以直接访问的局部变量,采用图的遍历算法,比如 DFS,将所有可达对象的标记位设置为 1(True)。
在清除阶段,线性扫描堆内存,将不可达对象直接释放,将可达对象的标记位重新设置为 0,为下一次标记清除做准备。
标记清除的优点是可以处理循环引用,缺点是算法会暂停程序的执行。为了优化垃圾回收的性能,可以使用分代回收算法。
分代回收
分代回收基于弱代假设:
弱代假设,英文是
Weak Generational Hypothesis,指大多数年轻对象存活时间较短,而老对象倾向于存活较长时间。
Python 将所有对象分为 0、1、2 三代,并用三个链表将它们串联起来,所有的新建对象都是 0 代对象。
当分配对象 Object Allocation 和释放对象 Object Deallocation 两者的差值达到 700 的时候,就会启动垃圾回收。每次垃圾回收都会遍历 0 代链表,如果对象存活,则升级为下一代对象。每 10 次 0 代垃圾回收,会配合一次 1 代垃圾回收;每 10 次 1 代垃圾回收,会配合一次 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表示回收0、1、2代对象。默认为2。gc.get_threshold():返回垃圾回收的阈值,是一个三元组,默认为(700, 10, 10)。gc.set_threshold(a, b, c):设置垃圾回收的阈值。gc.get_count():返回垃圾回收的计数器,是一个三元组。
参考
python的垃圾回收是采用的引用计数算法,而且在引用计数的基础上辅以标记-清除和分代回收算法。以引用计数算法来跟踪和回收垃圾;以标记-清除来解决对象产生循环引用造成无法回收的问题;以分代回收以空间换时间来进一步提高垃圾回收!