Python 深入浅出:内存泄漏排查与内存分析工具 tracemalloc 实战
很多开发者有一种误解:“Python 拥有自动垃圾回收机制(GC),所以在 Python 里绝对不可能发生内存泄漏。”
不幸的是,事实并非如此。在 Python 中,内存泄漏的表现通常是:程序占用的内存随着时间推移不断膨胀,直至触发操作系统的 OOM 机制被强行杀死。
Python 中常见的内存泄漏源自:全局变量不断累积状态、lru_cache 缓存未限制大小、以及第三方 C 扩展库没有正确释放底层的内存。
为了定位和修复这些棘手的内存故障,Python 3.4 内置引入了强大的内存分配追踪利器——tracemalloc。
本文将带您了解 Python 内存泄漏的成因,并掌握如何使用 tracemalloc 抓出占用内存的罪魁祸首。
一、 为什么 Python 也会内存泄漏?
Python 垃圾回收虽然会自动清理引用计数归 0 和循环引用的垃圾,但它无法替你做主释放“你依然持有引用的活动对象”。
常见的内存泄漏场景:
- 全局容器滥用:在模块级别定义了一个列表,然后不断地将业务数据
append进去,却没有及时清理。 - 失控的本地缓存:使用
@lru_cache装饰器,但设置了maxsize=None(无大小限制),且在运行中不断接收新的参数,导致缓存空间无限扩张。 - 僵尸线程与长连接:启动了线程但因为异常没有正确关闭,线程持有的局部变量和上下文就永远不会被释放。
二、 内存分析神器:tracemalloc 模块
tracemalloc 是 Python 标准库内置的硬核模块。它能够拦截并记录 Python 解释器每一次进行内存分配的调用栈(Call Stack)。
tracemalloc 核心 API:
tracemalloc.start(nframe=8):启动内存追踪。nframe代表记录的调用栈深度。tracemalloc.take_snapshot():拍摄当前的内存分配快照。snapshot.compare_to(other_snapshot, key_type):对比两个快照,分析内存的增量变化。
三、 实战:定位代码中的内存泄漏点
下面我们故意写一段包含“缓存泄漏”的脚本,并用 tracemalloc 对比快照,揪出到底是哪一行代码分配了最多的内存:
import asyncio
import tracemalloc
# 模拟一个失控的全局缓存
global_cache = []
def leaky_function():
# 每次调用向全局缓存中塞入大块字节数据
data = bytes(1024 * 1024) # 1MB
global_cache.append(data)
async def main():
# 1. 启动内存追踪
tracemalloc.start()
# 2. 拍摄初始状态的内存快照
snapshot1 = tracemalloc.take_snapshot()
print("📸 初始快照已拍摄。")
# 3. 运行一些可能导致泄漏的业务逻辑
for _ in range(10):
leaky_function()
await asyncio.sleep(0.1)
# 4. 拍摄第二张内存快照
snapshot2 = tracemalloc.take_snapshot()
print("📸 增量快照已拍摄。")
# 5. 对比两张快照,按“行号(lineno)”进行聚合对比
top_stats = snapshot2.compare_to(snapshot1, 'lineno')
print("\n🔥 --- 内存增量排名前 3 的代码行 ---")
for index, stat in enumerate(top_stats[:3], 1):
print(f"#{index} - 文件: {stat.traceback[0].filename} | 行号: {stat.traceback[0].lineno}")
print(f" 增量大小: {stat.size_diff / 1024:.2f} KB | 累计分配次数: {stat.count_diff}")
if __name__ == "__main__":
asyncio.run(main())
运行输出分析:
运行该程序,tracemalloc 会精准锁定并在终端打印出内存泄漏的位置:
#1 - 文件: main.py | 行号: 10
增量大小: 10240.00 KB | 累计分配次数: 10
它不仅告诉了你内存膨胀了 10MB,更直接把矛头指向了 leaky_function 内部的 bytes(1024 * 1024),让你一秒定位故障!
四、 进阶定位工具:objgraph
如果 tracemalloc 指出了是某个类的实例在暴增,但你不知道是哪一个全局变量或者链条持有了这些实例的引用,导致它们无法被垃圾回收,这时候可以使用第三方库 objgraph:
import objgraph
# 寻找前 3 个最耗费内存的对象
objgraph.show_most_common_types(limit=3)
# 自动生成一张 PNG 图片,绘制出到底是谁持有了 leaky_object 的引用
# objgraph.show_backrefs([leaky_object], max_depth=5, filename="leak_graph.png")
五、 总结
- 缓存加锁(LRU):使用缓存时,始终为其设置上限(如
maxsize=1024)。 - CI/CD 内存监控:对于长期运行的异步服务(如 FastAPI),可在集成测试中集成
tracemalloc,拍摄快照比对,把内存泄露扼杀在提测阶段。 - 合理运用工具:用
tracemalloc找内存分配源头,用objgraph找引用链持有关系。
掌握这套内存调优工具链,将为您编写高可用、长期稳定的生产级 Python 服务提供强有力的底层保障!
本站所有文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。



暂无评论
还没有人评论过本文,快来发表你的高见吧!