tracemalloc 必须在 python 入口第一行启动,否则无法捕获框架初始化阶段的内存分配;快照需启用 traceback_limit=25 并用 statistics('traceback') 对比,过滤 site-packages 干扰,结合 gc 和 objgraph 定位真实泄漏源。

tracemalloc 启动时机必须早于所有业务代码
线上内存问题往往在服务启动几小时后才暴露,但 tracemalloc 如果晚于框架初始化(比如 Django 的 setup()、FastAPI 的 app = FastAPI())再启用,就捕获不到关键对象的分配源头。它只记录启用后的分配行为,之前的所有堆内存分配完全不可见。
常见错误现象:get_traced_memory() 返回值很小,take_snapshot() 里几乎全是 <frozen importlib._bootstrap></frozen> 或空路径,说明没抓到业务逻辑的分配链。
- 在 Python 解释器最顶层入口(如
main.py开头第一行)调用tracemalloc.start() - 避免放在
if __name__ == "__main__":块内——WSGI/ASGI 服务器(如 gunicorn)可能不走这个分支 - 若用 systemd 或容器启动,确认 PYTHONPATH 和入口文件加载顺序,防止被中间层 wrapper 覆盖
快照采集要带 traceback 且限制深度
默认 take_snapshot() 不包含调用栈,只返回每个分配地址的大小,基本没法定位到哪行代码触发了内存增长。必须显式传参启用追踪,但又不能无限制记录——线上服务扛不住全栈帧开销。
使用场景:排查某个接口反复调用后 RSS 持续上涨,需要对比两次快照中新增的 top 分配点。
立即学习“Python免费学习笔记(深入)”;
系统特点:技术领先:系统基于被广泛使用的Windows平台开发,集百家之所长,技术领先、功能完备; 快速建店:只需简单设置,3分钟即可以建立一个功能完备的网上商城; 操作简便:软件操作界面由专业设计人员设计,采用人性化的布局,界面规范,操作简捷; 安装方便:只需传到您的虚拟空间即可; HTML编辑器:内置优秀的HTML在线编辑器; 可扩展性:软件构架灵活,考虑未来功能扩充之需要,具有较强的可扩展性
- 启动时加参数:
tracemalloc.start(traceback_limit=25)(25 是实测平衡点,再高 GC 压力明显上升) - 采集快照务必用:
snapshot = tracemalloc.take_snapshot(),不要省略括号 - 避免在请求处理中高频调用
take_snapshot()—— 单次耗时约 1–5ms,QPS 高时会拖慢响应 - 如果用
psutil.Process().memory_info().rss发现内存涨了 100MB,但快照里最大单条才 2MB,说明有大量小对象累积,此时需用filter_traces聚合相同 traceback
对比快照时别直接用 statistics('lineno')
statistics('lineno') 按「文件+行号」聚合,看似直观,但对动态生成代码(Jinja 模板、SQLAlchemy lazy loader、Pydantic model 实例化)极不友好——同一行可能对应 N 个语义完全不同的分配源,数据严重失真。
性能影响:当 trace 数超 10 万,statistics() 内部排序会吃掉 200+MB 临时内存,可能触发 OOM。
- 优先用
statistics('traceback'),能保留函数名和上下文层级 - 做 diff 时用
snapshot1.compare_to(snapshot2, 'traceback'),而非手动遍历stat.traceback - 过滤干扰项:加上
Filter(inclusive=False, filename_pattern="*/site-packages/*")排除第三方包噪音 - 如果发现 top 项全是
dict.__init__或list.__init__,说明问题在上层业务逻辑反复构造容器,而不是底层 C 扩展泄漏
线上跑着不能只靠 tracemalloc 定结论
它只能告诉你“哪些 Python 对象被分配了”,但无法区分是引用未释放、循环引用未被 GC 回收、还是底层 C 扩展(如 numpy array、PIL Image)持有的独立内存块。线上看到 RSS 涨了 500MB,tracemalloc 只统计出 80MB,剩下那 420MB 就得换工具查。
容易被忽略的地方:gunicorn worker 进程复用导致 tracemalloc 累积统计跨请求,而 get_traced_memory() 返回的是当前总分配量,不是增量。你看到的“涨了 50MB”可能是前 1000 个请求的累计,不是最近一次请求干的。
- 配合
gc.get_stats()看分代回收频率,若collected长期为 0,说明有对象逃逸出 GC 范围 - 用
objgraph.show_most_common_types(limit=20)快速看存活对象类型分布(需提前装 objgraph) - 对疑似 C 扩展内存,用
malloc_info()(Python 3.4+)或cat /proc/PID/smaps_rollup查Anonymous和Heap区域









