引用计数与垃圾回收

所有对象都有引用计数。给一个对象分配一个新名称,或是将其放入一个容器(如列表、元组或字典),都会增加该对象的引用计数,例如:

a = 37    # 创建一个值为 37 的对象
b = a     # 增加 37 的引用计数
c = []
c.append(b)  # 增加 37 的引用计数

这个例子创建了一个包含值 37 的对象,a 只是引用这个新创建的对象的一个名称。将 a 赋值给 b 时,b 就成了同一对象的新名称,该对象的引用计数因此增加。类似地,将 b 放到一个列表中时,该对象的引用计数将再次增加。在这个例子中,至始至终只有一个包含 37 的对象,所有其他操作都只是创建了对该对象的新引用。

使用 del 语句或者引用超出作用域(或者被重新赋值)时,对象的引用计数就会减少,例如:

del a    # 减少 37 的引用计数
b = 42   # 减少 37 的引用计数
c[0] = 2.0 # 减少 37 的引用计数

使用 sys.getrefcount() 函数可以获得对象的当前引用计数,例如:

>>> import sys
>>> a = 37
>>> sys.getrefcount(a)
7
>>>

多数情况下,引用计数比你猜测得要大得多。对于不可变数据(如数字和字符串),解释器会主动在程序的不同部分共享对象,以便节约内存。

当一个对象的引用计数归零时,它将被垃圾回收机制处理掉。但在某些情况下,在很多已不再使用的对象间可能存在循环依赖关系,例如:

a = { }
b = { }
a['b'] = b  # a 包含对 b 的引用
b['a'] = a  # b 包含对 a 的引用
del a
del b

在这个例子中,del 语句将会减少a 和 b 的引用计数,并销毁用于引用底层对象的名称。然而,因为每个对象都包含一个对其他对象的引用,所以引用计数不会归零,对象也不会被销毁(从而导致内存泄漏)。为了解决这个问题,解释器会定期执行一个周期检测器,搜索不可访问的对象周期并删除它们。解释器在执行过程中会被分配越来越多的内存,在此过程中,会定期运行周期检测算法。使用 gc 模块中的函数可以准确调整和控制该算法的行为。