
文章目录内存突然飙升——Python 内存泄漏的排查工具箱tracemalloc / objgraph / weakref导入语1 ~ 为什么 Python 还会有内存泄漏1.1 引用计数失效的场景1.2 全局容器无限膨胀2 ~ 工具一tracemalloc——精确到行号的内存追踪2.1 基础用法2.2 在 Django 视图或定时任务中追踪2.3 真实排查——定时任务的内存膨胀3 ~ 工具二objgraph——可视化对象引用链3.1 安装3.2 查看哪种类型的对象最多3.3 查看谁在引用这些对象4 ~ 工具三weakref——打破循环引用的设计模式4.1 什么是弱引用4.2 在信号和回调中使用弱引用4.3 WeakValueDictionary——自动清理的缓存5 ~ Django 生产环境内存泄漏排查清单思考 总结结尾内存突然飙升——Python 内存泄漏的排查工具箱tracemalloc / objgraph / weakref最新推荐文章于 2026-07-19 12:00:00 发布 | 阅读 1.0k 阅读 | 分类Python性能优化文章简介你的 Django 应用跑了一周后内存从 200MB 涨到 2GB——但 CPU 和数据库都正常。这就是内存泄漏的典型症状。Python 有垃圾回收为什么还会有内存泄漏本文从引用计数和 GC 失效的两个场景讲起——循环引用和全局缓存——然后逐一展开排查利器tracemalloc模块精确追踪内存分配位置、objgraph直观显示哪些对象数量异常、weakref弱引用打破循环引用链。每一步都配有真实的 Django 调试过程——一个定时任务因为缓存字典无限膨胀导致 OOM、一个 ORM 对象因为信号处理中的引用未释放导致内存泄漏。排查完后 Django 应用内存从 2GB 降回 200MB。 个人主页源码骑士❄专栏传送门《Android开发基础》《python基础课程》⭐️热衷从源码视角拆解技术底层原理将复杂架构讲得通俗易懂 源码骑士的简介5年Android Framework系统开发经验曾主导多项系统级性能优化专项技术栈覆盖Android系统全链路Binder/Handler/AMS/WMS/启动流程及Java后端全家桶Spring MyBatis Redis Oracle累计产出原创技术文章100篇文章以源码拆解为特色被读者评价为看一篇胜过啃一周文档导入语2022 年 6 月运维发了一张监控截图——一台跑 Django 的服务器内存使用从常驻 200MB 涨到了 1.8GB呈线性增长趋势。CPU 稳定在 10%数据库负载正常。没有报错但这样下去一周内就会 OOM。排查发现两个问题一个定时任务里缓存了一个全局字典字典不断膨胀从未清理——这是典型的内存泄漏。另一个是信号处理函数中持有了 ORM 对象的引用对象被循环引用卡住GC 无法回收。Python 有垃圾回收但引用计数和分代 GC 各有一块盲区。本文把工具箱里的三件工具全摊开——tracemalloc、objgraph、weakref——每件工具都配实际案例。1 ~ 为什么 Python 还会有内存泄漏1.1 引用计数失效的场景classNode:def__init__(self,name):self.namename self.refNoneaNode(A)bNode(B)a.refb b.refa# 循环引用deladelb# 两个对象引用计数都不是 0——GC 要到下一次扫描才回收1.2 全局容器无限膨胀# 最隐蔽的泄漏——全局变量或类属性被持续添加数据_request_cache{}# 全局字典defprocess_request(user_id,data):ifuser_idnotin_request_cache:_request_cache[user_id][]_request_cache[user_id].append(data)# 这个字典从不清空——每一次请求都在往里面塞数据2 ~ 工具一tracemalloc——精确到行号的内存追踪2.1 基础用法importtracemalloc tracemalloc.start()# 你的代码data[1]*1000000# 分配 ~8MB# 取快照snapshottracemalloc.take_snapshot()# 按文件统计内存分配forstatinsnapshot.statistics(filename)[:5]:print(f{stat.traceback}:{stat.size/1024/1024:.2f}MB)2.2 在 Django 视图或定时任务中追踪# 在 View 中嵌入 tracemallocimporttracemalloc tracemalloc.start()defreport_view(request):snapshot_beforetracemalloc.take_snapshot()# 业务逻辑datagenerate_report()snapshot_aftertracemalloc.take_snapshot()diffsnapshot_after.compare_to(snapshot_before,lineno)forstatindiff[:5]:print(f{stat.size_diff/1024}KB:{stat.traceback})returnrender(request,report.html,{data:data})输出示例4821KB: D:\myproject\reports\utils.py:23 # data [x for x in range(1000000)] 125KB: D:\myproject\reports\views.py:8 # context {data: data}精确到代码行——这就是 tracemalloc 的价值。不是泛泛的内存变大了而是reports/utils.py 第 23 行分配了 4.8MB。2.3 真实排查——定时任务的内存膨胀那个 2022 年泄漏的定时任务用tracemalloc定位后输出48MB: D:\myproject\tasks\sync.py:31 # _cache[user_id].append(data) 12MB: D:\myproject\tasks\sync.py:28 # if user_id not in _cache: _cache[user_id] []一眼看到——全局字典_cache在不断增长且从未清除。修复方案加了一条_cache.clear()在任务结束时调用或者改用 LRU 缓存自动淘汰旧条目。3 ~ 工具二objgraph——可视化对象引用链3.1 安装pipinstallobjgraph3.2 查看哪种类型的对象最多importobjgraph# 查看最多的对象类型objgraph.show_most_common_types(limit10)# 输出可能# dict 18234# function 12341# list 8923# BorrowRecord 7641 ← 这个类型为什么这么多BorrowRecord有七千多个实例——而总共只有 200 个用户和 500 本书。这个数字异常——说明 ORM 对象没有被回收。3.3 查看谁在引用这些对象# 查看最新代中没有被回收的 BorrowRecord 对象rootsobjgraph.get_leaking_objects()forobjinroots:ifisinstance(obj,BorrowRecord):print(obj,obj.id)然后追踪引用链# 画出引用链图importrandom leaks[objforobjinrootsifisinstance(obj,BorrowRecord)]ifleaks:objgraph.show_backrefs(leaks[:3],filenameleak.png,max_depth5)生成的图像会显示——这些 BorrowRecord 被某个全局字典_signal_cache引用着而_signal_cache被一个函数闭包捕获——导致引用链无法断开。4 ~ 工具三weakref——打破循环引用的设计模式4.1 什么是弱引用importweakrefclassCache:def__init__(self):self.data{}cacheCache()weak_refweakref.ref(cache)# 弱引用——不影响引用计数print(weak_ref())# 输出对象delcache# 强引用没了对象被回收print(weak_ref())# 输出 Noneweakref创建的引用不会增加引用计数——被引用对象可以在所有强引用都消失后被正常回收。4.2 在信号和回调中使用弱引用importweakrefclassSignalManager:管理回调——避免因为回调持有对象引用导致内存泄漏def__init__(self):self._receivers[]defconnect(self,receiver):# 用弱引用保存回调——不影响接收者对象的生命周期self._receivers.append(weakref.ref(receiver))defsend(self,signal):alive[]forrefinself._receivers:receiverref()ifreceiver:# 还活着则调用receiver(signal)alive.append(ref)# 保留这个引用self._receiversalive# 清掉已失效的引用4.3WeakValueDictionary——自动清理的缓存fromweakrefimportWeakValueDictionaryclassModelCache:def__init__(self):self._cacheWeakValueDictionary()# key → 弱引用值defput(self,key,obj):self._cache[key]objdefget(self,key):returnself._cache.get(key)cacheModelCache()# 当外部对 obj 的引用全部消后——cache 中的条目自动消失5 ~ Django 生产环境内存泄漏排查清单全局字典/列表——检查所有_cache {}、_data []这种全局容器确保它们有限增长信号回调——post_save、post_delete等信号处理函数中不要持有对发送者sender的强引用类属性——MyClass.big_list []在所有实例间共享——不断添加会膨胀闭包——定时任务中的闭包如果捕获了外部的大列表在任务重复执行时会造成累积Django QuerySet——被.all()或其他惰性查询创建后如果不被释放所有缓存的对象都留在内存排查命令python-mtracemalloc--tracebackmyproject/manage.py runserver思考 总结内存泄漏三件工具的适用场景工具什么时候用输出什么tracemalloc精确追踪哪行代码分配了多少内存分配大小 文件 行号objgraph排查哪种类型的对象数量异常类型计数 引用链可视化weakref设计上预防循环引用和信号泄漏弱引用不增加引用计数结尾内存排查工具箱讲完了。感谢阅读源码骑士 — 源码级拆解从底层看透技术关注跟博主一起从源码视角深耕底层原理❤️点赞让优质内容被更多人看见⭐收藏核心知识点存好随用随查评论分享你的经验或疑问一起交流一键四连别忘了给博主一键四连️寄语tracemalloc 告诉你哪个函数偷了你的内存objgraph 告诉你谁还在引用它。结语Python 内存泄漏不是靠猜能解决的——tracemalloc objgraph weakref 三件套定位→分析→修复一条龙。下篇进入并发模型对比——多线程 vs 多进程 vs 协程。一键四连