循环引用本身不会导致内存泄露,只有与__del__、weakref回调或某些c扩展共存时gc才会放弃回收;应使用gc.get_referrers()定位持有对象的引用源,而非gc.get_referents()。

循环引用本身不会导致内存泄露——只有当它和 __del__、weakref 回调或某些 C 扩展(如旧版 numpy view)共存时,GC 才会放弃回收,真正引发泄露。
用 gc.get_referrers() 定位“谁在偷偷留着对象”
别从被引用对象出发查 gc.get_referents(),那只会看到它指向了谁;真正卡住内存的是“谁还拿着它”。gc.get_referrers(obj) 才是关键入口。
- 先复现疑似泄露场景(比如反复创建某类实例后不释放),再手动触发
gc.collect() - 用
gc.get_objects(type=MyClass)拿到残留实例,挑一个obj开始查 - 重点关注返回结果里的:
dict(尤其是模块级、类__dict__)、list、tuple、函数的__closure__、以及module本身 - 如果看到
@lru_cache装饰的函数出现在 referrers 里,检查是否缓存了带状态的实例——这是高频陷阱
警惕 __del__ 和弱引用回调联手“封印”GC
只要循环中任意一个对象定义了 __del__,整个环就进 gc.garbage,不再被自动清理。更隐蔽的是:你没写 __del__,但用了 weakref.ref(obj, callback),而 callback 是闭包,隐式持有了 obj 或其属性,又形成新强引用链。
- 快速验证:临时注释掉所有
def __del__(self):,再测内存是否回落 - 避免在
__del__中访问日志器、全局字典等可能已被销毁的对象,否则异常会中断 GC 流程 - 优先用
weakref.finalize(obj, func)替代手动管理weakref.ref+ 回调,它不闭包捕获对象,更安全 - 检查第三方库(如
requests.adapters.HTTPAdapter)是否内部用了弱引用 + 回调,升级到 2.30+ 版本通常已修复
用 objgraph 可视化强引用路径,一眼揪出断点
objgraph 不是标准库,但它能生成 PNG 引用图,直接标出从根(如 module)到目标对象的最短强引用路径——比手动一层层 get_referrers() 高效得多。
- 安装:
pip install objgraph - 常用命令:
objgraph.show_backrefs([obj], max_depth=5, filename='ref.png') - 图中实线代表强引用,虚线是弱引用;重点看哪条实线本该在对象生命周期结束时断开却一直挂着
- 特别注意
functools._lru_cache_wrapper、threading.local、logging.Logger这些常作为“意外根节点”出现
真正难处理的不是循环结构本身,而是那些看似无害、实则把循环对象钉死在内存里的“弱引用回调”或“装饰器缓存”。一旦发现 gc.garbage 非空,优先查 __del__ 和 weakref 使用点,比盲目破环更省时间。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











