先确认是否真泄漏:空载下用psutil监控RSS,若单向上涨且gc.collect()后不降、objgraph.show_growth()显示某类对象持续增加,则为真泄漏;否则多为缓存增长。

怎么确认是不是真泄漏,而不是缓存增长?
别急着改代码,先看内存曲线是否“单向上涨”。用 psutil.Process().memory_info().rss 每 10 秒打一次点,空载(无请求)下跑 5 分钟:如果 RSS 还在涨,基本就是泄漏;如果涨完能回落,可能是缓存或 GC 延迟。
重点看两个信号:
-
gc.collect()后内存几乎不降 → 强引用没断,对象卡在 GC 队列外 -
objgraph.show_growth()显示某类对象(比如dict、list、自定义类实例)数量持续增加 → 锁定嫌疑类型
全局缓存/容器没清理,怎么办?
模块级变量如 cache = {}、_buffer = [] 是高频泄漏源。它们生命周期与模块一致,只要没显式清空,就一直占内存。
- 加 size 限制和淘汰逻辑:比如用
collections.OrderedDict实现 LRU,或每次写入后检查len(cache) > 1000就cache.popitem(last=False) - 优先用
weakref.WeakValueDictionary替代普通dict,尤其缓存的是短命对象(如临时 handler、widget 实例) - 避免在函数里反复
append到模块级 list,改用局部变量 + 返回值,或加atexit.register(lambda: _buffer.clear())做兜底
回调绑定没解绑,怎么查?
PyQt、aiohttp、Tornado、自定义事件总线里,connect() / add_callback() 后没配对 disconnect() / remove_handler(),会导致被监听对象无法回收。
调用 Cutout.Pro 视觉处理 API 进行背景移除、人像抠图和照片增强,支持文件上传与图片 URL 输入。
立即学习“Python免费学习笔记(深入)”;
- 检查所有注册点,确保销毁路径上有对应解绑调用 —— 不要只写在
__del__里(它不一定触发) - 用上下文管理器封装:比如
with event_bus.listen(event, handler): ...,退出时自动解绑 - 避免闭包捕获整个实例:
lambda: self.process()会强持self;改用weakref.ref(self)或显式传参handler=lambda obj=self: obj.process()
循环引用 + __del__,为什么最危险?
一旦对象有 __del__ 方法,且陷入循环引用,CPython 的 GC 会把它放进 gc.garbage 列表,永不回收 —— 这不是 bug,是设计行为。
- 禁用
__del__:改用contextlib.closing、__exit__或weakref.finalize(obj, cleanup_func) - 启用调试:
gc.set_debug(gc.DEBUG_UNCOLLECTABLE),然后gc.collect(),看终端是否打印 “uncollectable” 对象 - 用
objgraph.find_backref_chain(obj, lambda x: hasattr(x, '__module__') and x.__module__ == '__main__')找到谁在顶层持引用,常是模块变量或线程局部存储
真正难的不是发现泄漏,而是确认那个“不该活这么久”的对象,到底被哪条隐式引用链钉死了 —— 往往要结合 objgraph 和 gc.get_referrers() 多层反查,一次没定位准,就换 predicate 条件再试。

















