人人都会AI编程

4.2 引用计数原理

更新时间:2026-07-12

Python 内存管理的核心机制是引用计数。简单说:每个对象内部都有一个计数器,记录着当前有多少个变量或数据结构在引用它。当计数降到 0 时,Python 就自动回收这块内存,程序员几乎无需手动管理。

引用计数的增减规则

理解引用计数,关键是知道什么操作会让计数加 1,什么会让它减 1。

计数增加(+1)的典型场景

  • 创建对象:x = 3.14,整数对象 3.14x 引用,计数为 1。
  • 赋值传递:y = xy 也指向同一个对象,计数变为 2。
  • 作为参数传入函数:func(x) 在函数调用期间,形参会增加对象的引用计数。
  • 放入容器:lst.append(x)dct[key] = x,容器内部多了一个对该对象的引用。

计数减少(-1)的典型场景

  • 变量被重新赋值:x = 100,原先 3.14 对象的计数减 1。
  • 变量离开作用域:函数执行完毕后局部变量销毁,引用的对象计数减 1。
  • 显式删除:del yy 不再引用该对象,计数减 1。
  • 从容器中移除:lst.remove(x) 或容器本身被销毁,内部所有元素的引用计数都会相应减少。

你可以通过 sys.getrefcount() 查看一个对象的当前引用计数(注意它会因为参数传入而临时 +1,所以结果比实际多 1)。

>>> import sys
>>> a = []
>>> sys.getrefcount(a)   # 2: a 变量 + 传给 getrefcount 的参数
2
>>> b = a
>>> sys.getrefcount(a)   # 3
3
>>> del b
>>> sys.getrefcount(a)   # 2
2

引用计数的优势

  • 实时回收:对象从可达变为不可达的那一刻,内存就立即被释放,不会等到某个时间点再统一清理。
  • 简单、无停顿:没有“垃圾回收器”造成的全局暂停,适合对响应时间敏感的场景。

引用计数的致命缺陷:循环引用

引用计数最大的问题是无法处理循环引用。当两个或多个对象互相引用,形成闭环时,即使外部没有任何变量再指向它们,它们的计数也永远不会归零,导致内存泄漏。

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

a = Node(1)
b = Node(2)
a.next = b
b.next = a   # 形成环

del a
del b        # 两个 Node 对象仍然互相引用,计数各为 1,内存无法释放

在这个例子中,ab 被删除后,程序已经没有任何方式访问这两个 Node 对象,但它们内部的 next 属性互相持有引用,导致计数永远为 1。如果没有额外机制,这块内存就“泄漏”了。

循环引用的解决方案

正因为引用计数无法解决循环引用,Python 引入了一个额外的垃圾回收器(GC)专门处理这种情况。它使用“标记-清除”算法定期找出并回收这些孤立但互相引用的对象环。这个补充机制让 Python 的内存管理既保留了引用计数的实时性,又规避了循环引用带来的内存泄漏。

实用要点

  • 写代码时,大多数情况不需要刻意考虑引用计数,Python 会自动打理。
  • 重点是要注意循环引用,尤其是在使用了自定义对象互相引用、或者双链表/树等结构时,尽量在不再需要时手动断开引用(比如置为 None),避免给 GC 造成额外负担。
  • 对于长时间运行的服务端程序,如果怀疑有内存泄漏,可以借助 tracemalloc 模块或 objgraph 库分析哪些对象没有按预期释放,通常都能定位到循环引用问题。