Python 垃圾回收机制

11 阅读5分钟

引用计数

Python 中的每个对象都有一个引用计数器,用于跟踪有多少引用指向该对象。当引用计数降为0时,对象会被自动销毁,内存被释放。

引用计数的工作方式:

  • 创建对象时,引用计数设为1
  • 当对象被引用时(赋值给变量、添加到容器等),引用计数+1
  • 当引用被删除时(变量超出作用域、被重新赋值、从容器中移除等),引用计数-1
  • 当引用计数为0时,对象被销毁
import sys

# 创建对象
a = []
# 查看引用计数
print(sys.getrefcount(a) - 1)  # 输出1(减1是因为getrefcount本身会创建一个临时引用)

# 增加引用
b = a
print(sys.getrefcount(a) - 1)  # 输出2

# 删除引用
del b
print(sys.getrefcount(a) - 1)  # 输出1

标记清除

循环引用

引用计数的主要缺点是无法处理循环引用。例如,如果对象A引用对象B,而对象B又引用对象A,即使它们不再被程序使用,它们的引用计数也不会降为0。

class Node:
    def __init__(self, name):
        self.name = name
        self.ref = None
    
    def __repr__(self):
        return f"Node({self.name})"

# 创建循环引用
a = Node("A")
b = Node("B")
a.ref = b
b.ref = a

# 删除外部引用
del a
del b

# 此时,虽然Node A和Node B的引用计数都是1(互相引用),
# 但它们实际上已经不可达,成为了垃圾

标记清除

标记清除算法是一种追踪式垃圾回收算法,主要分为两个阶段:

  1. 标记阶段(Mark Phase) :从"根对象"(如全局变量、栈变量等)开始,递归地遍历所有可达对象,并将它们标记为"活动"。
  2. 清除阶段(Sweep Phase) :扫描整个堆内存,将所有未被标记的对象视为垃圾并回收它们的内存。

容器对象追踪

Python只对可能产生循环引用的容器对象(如列表、字典、类实例等)进行跟踪,而不跟踪像数字、字符串这样的不可变对象,因为它们不可能形成循环引用。

三色标记法

Python使用"三色标记法"来实现标记清除:

  • 白色 :未被访问的对象,初始状态下所有对象都是白色
  • 灰色 :已被访问但其引用尚未被完全检查的对象
  • 黑色 :已被访问且其所有引用都已被检查的对象 算法流程:
  1. 初始时,所有对象都是白色
  2. 将所有根对象标记为灰色,并放入灰色集合
  3. 从灰色集合中取出一个对象,将其所有引用的对象标记为灰色并加入灰色集合,然后将该对象标记为黑色
  4. 重复步骤3,直到灰色集合为空
  5. 此时,所有白色对象都是不可达的垃圾,可以被回收

分代回收

标记清除与分代回收其实是结合的,只是在关键词上我们通常进行这样记忆。

Python的垃圾收集器采用了分代回收策略,将对象分为三代:

  1. 第0代(年轻代):新创建的对象
  2. 第1代(中年代):经过一次垃圾收集后仍然存活的对象
  3. 第2代(老年代):经过两次垃圾收集后仍然存活的对象

分代回收的核心思想是基于"弱代假说":大多数对象在创建后很快就会变成垃圾。因此,垃圾收集器会更频繁地检查年轻代对象,而较少检查老年代对象。

  • 当第0代对象数量超过阈值时,触发第0代垃圾收集
  • 当第0代垃圾收集达到一定次数后,触发第1代垃圾收集
  • 当第1代垃圾收集达到一定次数后,触发第2代垃圾收集

默认的阈值是(700, 10, 10),意味着:

  • 当第0代对象数量超过700时,触发第0代垃圾收集
  • 每10次第0代垃圾收集,触发一次第1代垃圾收集
  • 每10次第1代垃圾收集,触发一次第2代垃圾收集
import gc

# 查看当前阈值
print(gc.get_threshold())  # 默认(700, 10, 10)

# 手动触发垃圾收集
gc.collect()

缓存机制

为了提高小对象的内存分配和释放效率,Python实现了内存池机制:

  • 对于小于512字节的对象,Python使用内存池进行管理
  • 当对象被销毁时,其内存不会立即返回给操作系统,而是放回内存池以供后续使用
  • 这减少了频繁向操作系统申请和释放内存的开销

其他扩展

垃圾回收机制

Python提供了gc模块,允许程序员控制垃圾回收:

import gc

print(gc.isenabled())  # 查看 GC 状态 输出:True(默认启用)

# 禁用自动垃圾回收
gc.disable()

# 启用自动垃圾回收
gc.enable()

# 手动触发垃圾回收
gc.collect()

# 设置垃圾回收阈值
gc.set_threshold(700, 10, 10)

# 查看当前阈值
print(gc.get_threshold())  # 默认(700, 10, 10)

# 手动触发垃圾收集
collected = gc.collect()
print(f"收集了{collected}个对象")

# 查看各代对象数量
print(gc.get_count())

# 设置调试标志
gc.set_debug(gc.DEBUG_SAVEALL)

# 创建并删除循环引用
class A:
    pass

a = A()
a.self = a
del a

# 回收垃圾并查看残留对象
gc.collect()
print(gc.garbage)  # 输出:[<__main__.A object at ...>]

弱引用(Weak References)

Python还提供了弱引用机制,允许引用对象而不增加其引用计数:

import weakref

class Object:
    pass

obj = Object()
# 创建弱引用
weak_ref = weakref.ref(obj)

# 通过弱引用访问对象
print(weak_ref() is obj)  # True

# 删除原始引用
del obj
# 弱引用现在返回None
print(weak_ref())  # None

总结

Python 的垃圾回收机制通过 ​​引用计数(主)​​ 和 ​​分代回收(辅)​​ 结合,高效管理内存。引用计数能快速回收无引用对象,而分代回收解决循环引用问题。 另外 B 站有一个讲解不错的视频 Python垃圾回收 可供参考。