Python 进程内存持续上升:用 tracemalloc 定位仍存活的分配
相同批次反复处理后,进程占用一直变大。先在安静点抓两张 Python 分配快照,按行号看哪些分配一直存在。再清理可疑持有者,第三张快照用来验证这些对象是否消失。
1. 只比较一个已经知道的分配位置
保存为 memory_lab.py,运行 python memory_lab.py。
import gc
import tracemalloc
def fill(cache):
for key in range(40):
cache[key] = bytearray(128 * 1024) # tracked allocation
def measured_growth(before, after, line):
return sum(
stat.size_diff for stat in after.compare_to(before, "lineno")
if stat.traceback[0].filename == __file__ and stat.traceback[0].lineno == line
)
line = fill.__code__.co_firstlineno + 2
cache = {}
tracemalloc.start()
try:
before = tracemalloc.take_snapshot()
fill(cache)
# This temporary allocation has ended before the snapshot.
temporary = bytearray(1024 * 1024)
del temporary
retained = tracemalloc.take_snapshot()
growth = measured_growth(before, retained, line)
assert growth >= 5 * 1024 * 1024
print("retained payload: at least 5 MiB")
cache.clear()
gc.collect()
after = tracemalloc.take_snapshot()
assert measured_growth(before, after, line) == 0
print("after clearing cache: allocation line back to baseline")
finally:
tracemalloc.stop()
本机实测输出如下。
retained payload: at least 5 MiB
after clearing cache: allocation line back to baseline
2. 在真实负载里缩小范围
真实排查时先按 lineno 排序看正增长,再按 traceback 聚合追到调用入口。对照快照应在相近负载、相同并发和同一业务阶段采集。例如每批队列排空后采样,避免把不同数量的进行中请求误判为泄漏。
| 观察结果 | 下一步 |
|---|---|
| 固定分配行在多批次后持续净增长 | 检查缓存、队列或全局对象如何保留引用 |
| 清理引用后快照下降,进程 RSS 仍高 | 检查分配器复用及操作系统内存指标 |
| RSS 上升,Python 快照几乎不变 | 调查本机扩展、映射文件或其他分配来源 |
tracemalloc 给出分配位置。要确认谁持有引用,需要结合缓存条目数、对象生命周期和应用代码。启用追踪有开销,可以先在复现场景启用,提前开始采集以免漏掉基线。
小结
用同一批次边界采样,找出仍存活的分配,再通过释放引用后的快照验证原因。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| Python tracemalloc | https://docs.python.org/3.11/library/tracemalloc.html | 快照比较、行号和追踪范围 |
核验说明
- Python 3.11.9 验证保留的 bytearray 和清理后的快照。实验未用 RSS 推算 Python 对象大小。