引用计数
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(互相引用),
# 但它们实际上已经不可达,成为了垃圾
标记清除
标记清除算法是一种追踪式垃圾回收算法,主要分为两个阶段:
- 标记阶段(Mark Phase) :从"根对象"(如全局变量、栈变量等)开始,递归地遍历所有可达对象,并将它们标记为"活动"。
- 清除阶段(Sweep Phase) :扫描整个堆内存,将所有未被标记的对象视为垃圾并回收它们的内存。
容器对象追踪
Python只对可能产生循环引用的容器对象(如列表、字典、类实例等)进行跟踪,而不跟踪像数字、字符串这样的不可变对象,因为它们不可能形成循环引用。
三色标记法
Python使用"三色标记法"来实现标记清除:
- 白色 :未被访问的对象,初始状态下所有对象都是白色
- 灰色 :已被访问但其引用尚未被完全检查的对象
- 黑色 :已被访问且其所有引用都已被检查的对象 算法流程:
- 初始时,所有对象都是白色
- 将所有根对象标记为灰色,并放入灰色集合
- 从灰色集合中取出一个对象,将其所有引用的对象标记为灰色并加入灰色集合,然后将该对象标记为黑色
- 重复步骤3,直到灰色集合为空
- 此时,所有白色对象都是不可达的垃圾,可以被回收
分代回收
标记清除与分代回收其实是结合的,只是在关键词上我们通常进行这样记忆。
Python的垃圾收集器采用了分代回收策略,将对象分为三代:
- 第0代(年轻代):新创建的对象
- 第1代(中年代):经过一次垃圾收集后仍然存活的对象
- 第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垃圾回收 可供参考。