先发制人2026
72.36M · 2026-03-22
在上一篇文章中,我们剖析了 Python 内存管理的第一道防线——引用计数。它实时、高效,却在“循环引用”面前束手无策。如果 Python 只有引用计数,那么复杂的对象网络将变成内存泄露的重灾区。
为了补齐这块短板,Python 引入了第二道防线:垃圾回收(Garbage Collection, GC)机制,主要通过“标记-清除”和“分代回收”技术,扫除那些躲在角落里的“僵尸对象”。
我们先用一段代码现场还原引用计数的“无力感”。
Python
import gc
import sys
class Node:
def __init__(self, name):
self.name = name
self.next = None
# 1. 构造循环引用
a = Node("A")
b = Node("B")
a.next = b
b.next = a
# 2. 查看当前引用计数(通常是 2:变量引用 + next 指针引用)
print(f"A 的计数: {sys.getrefcount(a) - 1}")
# 3. 销毁外部引用
del a
del b
发生了什么? 此时,变量 a 和 b 已经从作用域消失,但对象 A 的 next 指向 B,对象 B 的 next 指向 A。它们的引用计数都停留在 1。
在引用计数眼中,它们“还没死”;但在程序员眼中,它们已经变成了无法触达的内存垃圾。这就是所谓的**“死亡环联”**。
为了处理这些顽固垃圾,同时又不至于频繁扫描整个内存导致卡顿,Python 采用了分代回收策略。其核心思想是:存活时间越久的对象,越不可能是垃圾。
Python 将内存中的对象分为三代:0 代(Generation 0) 、1 代(Generation 1) 和 2 代(Generation 2) 。
所有新创建的对象都会被放入 0 代。由于大多数对象都是“朝生夕死”(如局部变量),0 代的扫描频率最高。
如果一个对象在 0 代回收中幸存下来(即它还有效),它就会被“晋升”到 1 代。
在 1 代回收中依然屹立不倒的对象,最终进入 2 代。这里存放的是全局变量、模块对象等长生命周期对象。
gc.collect()?在处理海量数据(如从数据库加载千万级记录)时,你会发现内存占用在短时间内剧增,但 Python 的自动回收似乎“反应迟钝”。
Python 的自动 GC 是基于对象数量(Threshold)的,而不是基于内存占用大小的。
在删除大型数据结构或结束大规模循环后,手动调用 gc.collect() 可以强制清理 0 代、1 代甚至 2 代中的循环引用,将内存归还给操作系统。
Python
import gc
# 处理完大量数据后
del big_data_list
# 强制进行全量垃圾回收,打破可能的循环引用
gc.collect()
gc.set_threshold 的艺术高级开发者可以通过调整回收阈值,在“回收频率”与“程序性能”之间寻找平衡。
Python
# 获取当前阈值,默认通常是 (700, 10, 10)
print(gc.get_threshold())
# 调优:在高并发、短对象极多的场景下,可以调大 0 代阈值
# 减少 GC 扫描导致的 CPU 抖动
gc.set_threshold(2000, 15, 15)
调优逻辑:
虽然有 GC 兜底,但作为全栈/高级开发,我们的目标应该是:尽量不产生循环引用。
weakref.ref。弱引用不会增加引用计数,也不会阻碍对象被回收。__del__ 的坑:在老版本 Python 中,定义了 __del__ 方法的对象若涉及循环引用,GC 无法自动处理。虽然 Python 3.4+ 解决了这个问题,但依然建议慎用 __del__。