99%的python oom是内存泄漏而非内存不足:vmrss持续上涨且gc.collect()后不回落,gc.get_count()[0]≥650且gc.collect(0)恒返回0;用tracemalloc.start(25)比对快照,过滤增量>10kb项,重点查dict.__setitem__、list.append、__init__;修复需主动断引用链,禁用无限制全局缓存,改用lru_cache或weakset,显式清理闭包缓存,并在生命周期终点del+置空+c扩展对象手动释放。

直接说结论:99% 的 Python OOM 不是内存不够,而是对象没被释放——gc.collect() 喊一百遍也没用,关键得切断引用链。
怎么确认是内存泄漏,不是单纯内存不足
先看两个硬指标,别猜:
-
VmRSS(物理内存)持续单向上涨,且gc.collect()后几乎不回落 -
gc.get_count()返回的第 0 代计数长期 ≥ 650,同时gc.collect(0)返回值恒为 0(说明根本没回收到东西) - 用
psutil.Process().memory_info().rss每 10 秒打点,画出曲线——如果斜率稳定为正,基本就是泄漏
tracemalloc 快照比对必须带深度和过滤
tracemalloc.start() 默认只记 1 层调用栈,完全没用;不加过滤则满屏都是 list.append 这类通用操作,看不出谁在“养”对象。
- 启动时必须指定深度:
tracemalloc.start(25)(25 层足够定位到业务函数) - 两次快照用
snapshot2.compare_to(snapshot1, 'lineno'),再手动过滤增量 > 10KB 的条目 - 重点关注
dict.__setitem__、list.append、__init__出现在高增长行——它们背后大概率是全局缓存、静态列表或闭包 cache
三类高频泄漏点的修复姿势
别写“等 GC 来收”,要主动断链:
- 全局字典/列表:改用
functools.lru_cache(maxsize=100),或加 TTL + 定期cache.clear();绝对不用_cache = {}无脑塞 - 类静态变量持实例:把
cls.instances.append(self)改成weakref.WeakSet(),否则实例生命周期等于进程生命周期 - 闭包缓存(如装饰器):避免在闭包内建大字典;若必须缓存,显式暴露
clear_cache()方法,并在关键路径(如请求结束)调用
生产环境别依赖自动 GC
Python 的 GC 阈值默认是 (700, 10, 10),意思是第 0 代对象超 700 个才触发一次扫描。高并发服务里,每秒新增几千对象,GC 根本追不上节奏。
真正有效的做法是:在请求/任务/会话生命周期终点,强制执行 del + gc.collect() + 主动置空(如 obj.retriever = None),尤其对 numpy.ndarray、pandas.DataFrame、LLM 实例这类 C 扩展对象——它们的底层内存压根不受 Python GC 管。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











