Python 的内存管理通常由解释器自动完成,但在长时间运行的服务、缓存系统或批量数据处理任务中,某些对象可能被意外保留,导致内存占用持续上升。标准库中的 tracemalloc 模块可以记录 Python 层内存分配的调用栈,帮助开发者把内存增长定位到具体代码位置。它从 Python 3.4 起成为标准库的一部分,无需安装第三方依赖,使用门槛较低。

tracemalloc 的跟踪机制与启动方式
tracemalloc 的核心工作方式是在 Python 分配内存时记录当前调用栈。调用 tracemalloc.start() 后,解释器会保存每一次内存分配对应的栈帧,开发者可以指定保存的栈帧深度。栈帧越深,得到的调用链越完整,但也会带来更多的记录开销和内存消耗。因此在实际使用中,通常根据排查需要设置一个合理的深度,例如 25 层已经能够覆盖大多数业务调用层级。
为了保证覆盖到全部内存行为,建议在程序入口的最早位置开启跟踪。如果跟踪启动得太晚,启动之前的分配记录将无法被捕获,可能导致内存泄漏的源头被遗漏。对于 Web 服务或后台任务,可以在进程启动时开启,而不是等到某个请求处理函数内部再开启。
import tracemalloc
# 开启内存跟踪,保存 25 层调用栈
tracemalloc.start(25)
def build_large_list():
# 模拟创建一个较大的列表对象
return [item for item in range(100000)]
cache = build_large_list()
上面的代码在程序入口处启动了 tracemalloc,并保留 25 层调用栈。build_large_list() 创建了一个包含十万个整数的列表,后续可以通过快照观察这段分配占用的内存大小。需要说明的是,tracemalloc 跟踪的是 Python 层面的分配,整数、列表、字符串等对象都会进入统计范围。
获取内存快照并理解统计输出
开启跟踪后,可以在任意时刻调用 tracemalloc.get_snapshot() 获取当前内存快照。快照中保存了所有尚未释放的 Python 对象分配信息,包括分配位置和占用大小。拿到快照后,通常需要调用 snapshot.statistics() 进行聚合,以便快速找到占用最高的代码位置。
import tracemalloc
tracemalloc.start()
# 执行业务逻辑,生成一组数据
data = [value * 2 for value in range(50000)]
snapshot = tracemalloc.get_snapshot()
# 按代码行号统计,并按内存占用降序排列
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
print(stat)
statistics() 方法支持不同的统计维度。lineno 按文件行号聚合,适合快速定位到具体某一行;filename 按文件聚合,适合从整体上观察哪个模块占用最高;traceback 则保留完整调用栈,适合需要深入分析调用链的场景。每条统计记录中的 size 表示该位置当前占用的总字节数,count 表示分配次数。需要注意的是,这里的 count 并非当前存活对象的数量,而是累计分配次数减去释放次数的结果。
| 统计维度 | 适用场景 |
|---|---|
| lineno | 快速查看某一行代码占用了多少内存 |
| filename | 查看哪个文件整体内存占用最高 |
| traceback | 需要完整调用链时使用 |
同一个程序在不同统计维度下会有不同的表现。例如,一个工具函数被多个模块调用,按 lineno 统计可能分散到不同调用点,而按 filename 统计则更能体现该函数所在文件的整体贡献。因此,在排查时可以先使用 filename 或 lineno 找到大致范围,再使用 traceback 查看完整调用关系。
通过快照对比定位内存增长
单次快照只能反映某一时刻的内存分布,无法区分哪些内存是历史遗留的,哪些是刚刚新增的。如果程序在运行一段时间后内存明显升高,更好的做法是在两个时间点分别获取快照,然后调用 compare_to() 得到差异统计。这样可以直接看到两次快照之间哪些代码位置新分配了最多内存。
import tracemalloc
tracemalloc.start()
first_snapshot = tracemalloc.get_snapshot()
# 模拟运行一段时间后新增缓存数据
cache = []
for _ in range(5):
cache.append(' ' * 10 ** 6)
second_snapshot = tracemalloc.get_snapshot()
# 对比两次快照,输出增长最多的 5 条记录
diff_stats = second_snapshot.compare_to(first_snapshot, 'lineno')
for stat in diff_stats[:5]:
print(stat)
在上述示例中,程序首先记录了一次初始快照,随后向 cache 列表中追加了五个较大的字符串。第二次快照与第一次快照对比后,compare_to() 会按照内存增长量降序排列差异记录。这样,开发者可以迅速看到 cache.append() 所在行成为内存增长的主要来源。如果这种增长在业务上是不合理的,就可以进一步检查是否存在缓存未清理、全局变量持续累积或其他保留引用的问题。
对比快照的方法非常适合周期性任务和请求处理场景。例如,可以在每次请求处理前获取一次快照,在处理完成后再获取一次快照,如果发现某次接口调用后内存明显上涨且未回落,就能通过差异记录找到对应代码行。
实际排查建议与注意事项
在真实项目中,不建议长期开启 tracemalloc,因为它会显著增加内存和 CPU 开销。更好的方式是在需要排查时临时开启,或者仅在开发环境、预发布环境中进行采样。
Pythontracemalloc内存泄漏修改时间:2026-07-30 22:42:22