恐怖解谜密室逃脱
109.73M · 2026-03-09
前言:你是否有了解过python 最核心、最基础的内存管理机制——引用计数(Reference Counting) 。
sys.getrefcount 的数字游戏在 Python 中,每一个对象创建时,都会自带一个名为 ob_refcnt 的属性。这个数字记录了当前有多少个变量(指针)正指向这个对象。
我们通过 sys.getrefcount() 这个“坚控摄像头”来观察它的生死时速:
Python
import sys
# 1. 创建一个对象
a = [1, 2, 3]
print(sys.getrefcount(a))
实验发现: 你可能以为输出是 1,但实际输出通常是 2。
知识点: sys.getrefcount(a) 在执行时,会将变量 a 作为参数传递给函数。在函数内部,形参也引用了该对象,导致计数临时 +1。所以,实际计数永远比你肉眼看到的变量数多 1。
对象的引用计数就像一个不停波动的计数器,以下四种行为是导致计数增加的“四大元凶”:
Python
a = [1, 2, 3] # 计数从 0 变 1 (加上临时引用共 2)
b = a # 计数再 +1
Python
def check(data):
print(sys.getrefcount(data)) # 进入函数,形参引用,计数再 +1
check(a)
Python
my_list = [a, a, 123] # a 被两次放入列表,计数 +2
Python
class MyObj: pass
obj = MyObj()
obj.attr = a # 作为对象的属性,计数 +1
当引用计数变为 0 的那一刻,Python 会毫不留情地立即回收该对象占用的内存。这就是对象“死亡”的瞬间。
触发计数减少的操作通常包括:
del 显式删除:del a 并不是删除对象,而是删除了“指向对象的标签”,让计数 -1。a = 456,原先 [1, 2, 3] 的计数 -1。这是 Python 内存管理最迷人的地方,也是它与 Java、Go 等语言最大的区别。
引用计数机制最大的优点是实时性。一旦计数归零,内存立即释放。
Java 的垃圾回收主要依赖可达性分析。它不会在引用断开的一瞬间回收内存,而是等到内存压力大到一定程度时,启动 GC 线程。
引用计数虽然高效,但它有一个致命的逻辑漏洞:它不识“连环计” 。
Python
# 构造一个互相伤害的死循环
a = {}
b = {}
a['next'] = b
b['next'] = a
del a
del b
惨剧发生: 虽然我们 del 了 a 和 b,但 a 引用着 b,b 也引用着 a。它们的计数永远停留在 1,无法归零。
这些对象成了内存中的“僵尸”,它们不再能被业务代码访问,却死死霸占着内存空间。
在 Python 的世界里,一个对象的寿命取决于它对外界是否还有“利用价值” (即引用数)。