Python 服务 RSS 持续上涨、容器反复 OOMKilled,而单次请求并不大。
进程 RSS 或 cgroup 内存随时间单调上升,重启后恢复;容器被 OOM Killer 终止(退出码 137),或在分配大对象时抛 MemoryError。常见于长期运行的服务、定时任务和 worker 进程。
先确认内存是否真的在涨,再看是 Python 对象还是 native 分配:
ps -o pid,rss,vsz,etime,cmd -p <PID>
grep -E 'VmRSS|VmSwap|VmData' /proc/<PID>/status
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.events
grep -E '^(anon|file|slab) ' /sys/fs/cgroup/memory.stat
memory.current 包含 page cache,判断泄漏要看 anon 和 memory.events 里的 oom_kill;VmRSS 才是进程常驻内存。接着看分配栈:
py-spy dump --pid <PID>
python -X tracemalloc=25 app.py
memray run --output /tmp/app.bin app.py
memray flamegraph /tmp/app.bin
memray attach <PID>
tracemalloc 只覆盖 Python 分配器,memray 能覆盖 C 扩展和 native 分配。在代码里做快照差分:
python - <<'PY'
import tracemalloc
tracemalloc.start(25)
run_workload()
for stat in tracemalloc.take_snapshot().statistics("lineno")[:10]:
print(stat)
PY
易误判:释放对象后 RSS 不下降不等于泄漏 —— pymalloc 和 glibc arena 会保留已释放的内存。要用 gc.collect() 后的 live 对象数、anon 斜率和 oom_kill 共同判断,而不是只看一次 RSS。
dict、list、@lru_cache(maxsize=None) 持续累积。close()。__del__ 或 C 扩展,导致对象进入 gc.garbage 无法回收。MALLOC_ARENA_MAX 未限制。tracemalloc 快照差分或 memray flamegraph 定位到具体分配行,再改代码。cachetools.TTLCache、lru_cache(maxsize=N))。with/contextmanager 管理 session、文件和连接;requests 复用 Session。ijson、iter_content),避免整块驻留。gc.collect() 后采样 len(gc.get_objects()) 与 gc.garbage,确认不再增长。memray、valgrind --tool=massif 或库自带诊断;升级有问题的扩展。MALLOC_ARENA_MAX=2,或换 jemalloc,观察 RSS 是否收敛。oom_kill。gc.collect() 后对象数不涨,最可能是什么问题?tracemalloc 看不到 numpy/C 扩展的分配?memory.current 很高但进程 RSS 不高,应该看哪个字段?anon、memray 和 RSS 斜率判断。tracemalloc 只跟踪 Python 内存分配器;未经过它的 C malloc/mmap 不会出现,需用 memray 或 valgrind。memory.stat 的 file 与 anon;file 高通常是 page cache,可回收,不能据此判定进程泄漏。