Python脚本运行内存持续升高,属于正常现象还是内存泄漏?
问题结论
你遇到的是可排查修复的内存泄漏问题,不属于Python正常内存波动,单纯扩容RAM无法适配后续脚本迭代、运行时长增加的需求,优先定位修复泄漏才是最优方案。
核心原因分析
Python分代GC中,第2代存储的是经过多次GC仍然存活的长生命周期对象,正常场景下2代对象数量会稳定在固定区间,不会随运行时长线性增长。你观测到2代对象从10.5万暴涨至110万,说明有大量本应被回收的临时对象被意外持有,GC无法释放,结合你的业务场景,常见泄漏原因如下:
- ORM层隐式缓存:绝大多数ActiveRecord实现默认会开启会话级身份映射(Identity Map)缓存,只要数据库会话未关闭、缓存未清空,所有查询生成的ActiveRecord对象都会被会话直接持有,哪怕你的业务代码已经不再引用这些对象,GC也无法回收。你复用同一个连接/会话处理所有数据的情况下,缓存会随运行时间持续膨胀。
- 循环引用+
__del__方法的特殊处理:如果你的ActiveRecord类自定义了__del__析构方法,Python GC遇到包含__del__方法的循环引用时会直接放弃回收,这类对象会被存入gc.garbage列表永久留存,不会被释放。 - 长生命周期变量隐式持有:你提到的大型关联图可能被全局列表/字典、类属性等长生命周期的变量意外引用,导致超出业务作用域后仍然存在可达引用,无法被GC识别为垃圾。
排查修复步骤
- 优先排查ORM缓存:确认你使用的ActiveRecord库的会话缓存机制,每处理完一批数据后手动调用会话的清空缓存接口(常见接口为
session.expunge_all()、session.clear()),或者每处理完一批数据就销毁旧会话新建会话,避免缓存无限积累。 - 验证析构方法问题:手动执行
gc.collect()后打印len(gc.garbage),如果该值不为0且其中大部分是你的ActiveRecord对象,说明存在带__del__的循环引用泄漏,要么删除__del__方法改用上下文管理器__exit__处理资源释放,要么在丢弃对象前手动解开关联图的循环引用。 - 精确定位泄漏点:使用
objgraph工具的objgraph.show_growth()方法,对比不同运行阶段的对象计数变化,直接定位增长最多的对象类型,顺藤摸瓜找到持有这些对象的长生命周期引用。
扩容建议
泄漏修复完成后,内存占用会稳定在固定阈值,不会随运行时长线性上涨。如果稳定后的内存占用仍然超出设备可用内存,再考虑扩容RAM即可,否则单纯扩容只会延迟内存耗尽的时间,无法从根本上解决问题。
内容的提问来源于stack exchange,提问作者Jesse Hogan
相关产品推荐
相关产品推荐

