记一次Python应用内存泄漏问题定位

前情提要

之前公司的一项目使用Airflow作为任务调度器,上线后,每隔一段时间就会出现Scheduler内存OOM问题,由于是Airflow是Python实现的,对于Python进程当挂掉后似乎没有dump文件,因此对于这个问题,我们一开始采取的方式就是啃代码,看能否通过代码走读发现一些端倪,可是实际排查先来毫无头绪,于是我查了查Python内存定位的相关技术文章,终于在若干工具的帮助下,解决了这个问题,本文将对本次问题定位进行回顾与总结。

Python进程内存泄漏的常见原因

我们知道Python是具有垃圾回收器的,一般情况下程序员不需要关注对象内存释放,但是这并不意味着我们写代码就高枕无忧了,往往还会有以下几种情况导致应用内存无法及时释放,从而造成内存泄漏问题。

  • 业务代码BUG: 代码中创建大量对象,虽然这些对象不再被访问,但是由于它们仍然被一些全局对象引用,GC无法释放这些对象。
  • 未正确关闭文件句柄: Python中的文件对象是与操作系统文件句柄相关联,如果文件对象没有被正确关闭,操作系统文件句柄无法释放,导致内存泄漏。
  • 为正确释放C扩展模块或外部库中的内存: 应用使用了C扩展模块或者外部库来扩展Python功能,如果其中存在内存泄漏则上层应用运行时也会内存泄漏。
  • 循环引用: Python 的垃圾回收机制是基于引用计数的,当一个对象的引用计数为 0 时,垃圾回收机制会自动回收这个对象。如果存在循环引用,即两个或多个对象相互引用,导致它们的引用数不会变为 0,垃圾回收机制无法回收这些对象,从而导致内存泄漏。

常用的问题定位工具

1、Objgraph

这是一个可以用来查看对象间引用关系的工具,比如对于一些大对象总是不释放的问题,可以通过这个工具分析出其到底是被哪些对象所引用。

工具安装

arduino 复制代码
pip install objgraph         // 安装objgraph
pip install xdot             // 安装可视化依赖库
pip install  graphviz        // 安装绘图依赖库
apt-get install graphviz     // 安装绘图工具

使用介绍

上面代码构造了一个循环引用的场景,通过objgrap可以看到对象引用关系图如下:

注意:此工具在生成对象间引用关系时是一个比较耗时的操作,需要确保执行频率不要太高以免影响业务代码流程,而且我们也没必要每次都要将内存中所有对象引用关系都搞出来,这样不仅耗时而且也难以分析。

2、pympler

此工具可以方便获取到当前内存使用情况,比如过去占用内存最多的前十个对象类型,对象个数以及占用内存字节。

工具安装

复制代码
pip install pympler

使用介绍

上述代码构造了一个字符串和一个字典,通过pympler可以看到str和dict类型在内存中数据量。

3、tracemalloc

tracemalloc是一个用于跟踪Python应用内存分配的工具,在python3.4以上此工具为标准库,所以无需安装。

使用介绍

我们可以看到tracemalloc不仅可以获取占内存最多的对象,同时也能看到改对象创建的堆栈。

如何定位问题

我们可以看到Airflow Scheduler每隔一段时间内存使用率就会增长到100%,然后OOM。

从现象大概能分析出应该是某种类型对象不停被创建但是有没有被GC回收导致的,那么我们就需要知道到底是什么类型对象一直在创建呢,我们先用pympler看看。在业务代码中实现如下方法,然后定期执行。

通过日志可以看到占内存最多的是str和dict,目前没有什么可分析的线索。

由于我们看到占内存最多的是str和dict这两种基本类型,所以很难去分析,所以我们可以用tracemalloc追踪下内存分配情况,看到底是哪些代码在分配这些内存。在业务代码总实现如下方法,然后定期执行。

根据堆栈初步得出结论在Scheudler从db中加载dag这里会有很多次的内存分配,但是为什么这些内存不释放,我们还是无从得知。

目前为止我们定位出内存泄漏大致发生在Scheudler解析dag文件的地方,同时内存中str和dict对象占多数,一般情况下我们重点关注dict,因为python对象都会包含很多dict类型数据。所以我们干脆将内存中的所有dict数据打印出来看看。

这时候我们可以看到内存中有很多taskinstance数据,这些taskinstance对应的dag实际上已经跑完了,那为什么还驻留在内存中呢?

到这一步,接下来就好办了,我们将包含taskinstance信息的dict的引用关系搞出来就OK了,这时候objgraph就派上用场了。我们实现如下方法:

执行后,我们能得到对象引用关系图如下:

通过对象引用关系图可以很清晰看到这些taskinstance都是被DagBag维护的,通过分析这里代码,终于定位到原因:Scheduler的DagBag会将所有要运行的Dag从DB中加载出来并缓存起来到其内部的dict中,但是当这些Dag跑完后并没有进行删除所以一直驻留在内存中,所以才会出现Scheduler跑一段时间内存就满了的情况。

总结

对于Python进程内存泄漏问题定位,可以按如下步骤进行:

  1. 先确认下内存中一直保持增长的是什么类型对象
  2. 看看内存分配最频繁的地方是哪里
  3. 将这类占内存比较多的对象打印出来进行分析,看看是否有业务上信息
  4. 将重点怀疑的对象通过objgraph打印出引用关系从而分析出内存泄漏的问题源自哪里
相关推荐
hhzz4 小时前
基于监控视频的水位尺自动识别技术方案与实现
python·opencv·yolo·图像识别·cv
yongche_shi4 小时前
ragas官方文档中文版(五十)
开发语言·python·ai·ragas·如何评估和改进 rag 应用
weixin_408099675 小时前
OCR批量识别图片方案:从手动处理到自动化API系统(Python/Java/PHP实战)
图像处理·python·ocr·文字识别·api调用·批量识别·石榴智能
AI行业学习5 小时前
Notepad++ 官方下载 + 完整安装 + 全套优化配置(2026最新)
开发语言·人工智能·python·前端框架·html·notepad++
大圣编程6 小时前
Python中continue语句的用法是什么?
开发语言·前端·python
云烟成雨TD6 小时前
LangFlow 1.x 系列【5】可视化编辑页面功能说明
人工智能·python·agent
geovindu8 小时前
python: Functional Options Pattern
开发语言·后端·python·设计模式·惯用法模式·函数式选项模式
tryCbest8 小时前
Python 文件操作
服务器·python
涛声依旧-底层原理研究所9 小时前
Agent 长任务可靠性设计:实现暂停、恢复、续跑与崩溃重启的完整方案
人工智能·python·系统架构
AC赳赳老秦9 小时前
防火墙规则批量配置实战:OpenClaw 自动生成模板、批量下发与合规性校验全解析
java·开发语言·人工智能·python·github·php·openclaw