使用Python内存分析器时刷新页面后函数内存占用持续增长原因排查
嘿,我来帮你捋捋这个内存持续增长的问题——我之前在Web应用里也碰到过类似的情况,用memory_profiler排查的时候头都大了,不过后来找到了几个常见的坑,给你参考下:
先排除工具本身的干扰
有时候@profile装饰器(尤其是memory_profiler库的实现)可能会保留一些统计数据,或者修改了函数的执行上下文,导致看起来内存一直在涨,但实际是工具本身的开销。你可以先去掉@profile,用Python自带的tracemalloc做个简单验证:
import tracemalloc @login_required def your_view(request): tracemalloc.start() snap_before = tracemalloc.take_snapshot() # 这里放你的函数逻辑 snap_after = tracemalloc.take_snapshot() top_diff = snap_after.compare_to(snap_before, 'lineno') print("内存增长TOP5:") for stat in top_diff[:5]: print(stat) tracemalloc.stop()
如果去掉@profile后内存还是持续涨,那问题就出在你的代码逻辑里了。
最常见的几个内存泄漏原因
1. 全局/模块级变量在不断累积
这是最容易踩的坑!比如你在函数里往全局列表、字典或者模块级的缓存容器里添加元素,这些对象的引用会一直存在,gc根本无法回收它们。举个例子:
# 模块级的全局变量,每次调用函数都往里加东西 request_cache = [] @login_required @profile def your_view(request): data = fetch_some_data(request) request_cache.append(data) # 这里每次调用都加,内存肯定涨! return render(request, 'page.html', {'data': data})
检查下你的函数里有没有修改全局变量、类的静态属性,或者自定义的缓存容器,有没有忘记清理这些内容。
2. 未正确关闭的资源/对象引用泄漏
比如打开的文件、数据库连接、网络会话对象,或者ORM查询出来的模型实例,被意外存到了长期存在的容器里,导致无法被回收:
- 用
open()的时候一定要用with语句自动关闭,不要手动打开后忘记close; - 数据库连接要放回连接池,或者用完就关闭;
- 第三方库的对象(比如requests的Session)要正确关闭,避免持有连接或缓存数据。
另外,如果你的代码里有回调函数、事件监听器,记得在函数结束时移除它们,不然这些回调会持有对象引用,导致gc无法回收。
3. 循环引用+自定义__del__方法
Python的gc虽然能处理普通的循环引用,但如果循环引用中的对象自定义了__del__方法,gc会不知道先调用哪个对象的析构函数,直接把这些对象放到不可回收的列表里,导致内存泄漏。检查下你用到的类有没有自定义__del__,如果有的话,尽量改成其他方式清理资源。
进一步排查的工具
如果上面的方法还没找到问题,可以试试这两个工具:
objgraph: 用来查看对象的引用链,找到谁在持有那些无法被回收的对象。比如你发现list对象一直在涨,可以用objgraph.show_backrefs()查看引用它的对象:import objgraph @login_required def your_view(request): # 函数逻辑 objgraph.show_most_common_types() # 看当前内存里最多的对象类型 # 如果找到了可疑对象,查看它的引用链 # suspect_obj = get_suspect_object() # objgraph.show_backrefs([suspect_obj], filename='backrefs.png')- 手动触发gc并查看回收情况: 在函数末尾手动调用
gc.collect(),并打印回收的对象数,看看gc到底有没有在干活:import gc @login_required @profile def your_view(request): # 函数逻辑 collected_count = gc.collect() print(f"本次调用回收了 {collected_count} 个对象")
如果每次调用都能回收一些对象,但内存还是涨,说明有对象的引用一直没被释放,得继续找根源。
内容的提问来源于stack exchange,提问作者Abi Waqas

