一次python内存溢出的排查

最近在我的项目中,出现了内存持续增长的情况。这也是我第一次碰到这种情况,以前在写c++都没试过,难得啊,所以记录,积累一下经验。
一、项目背景

生产者和消费者,操作kafka,使用的是pykafka库。消费者从kafka获取消息后,发送给业务处理服务,使用的是requests库。

二、工具
在此次过程中,使用过的工具或者库。
1)、memory_profiler库。
可以使用这个库监控内存的消耗,以及基于行的内存分析

2)、objgraph库。
可以显示占据python程序内存的前n个对象
可以统计一段时间内,对象增加减少的数量
可以以图片形式,展现对象的引用关系

3)graphviz工具
将objgraph库生成的dot文件生成图片

三、初次分析
使用python开发程序,内存泄露的几个基本原因:
1、存在循环引用,gc不能释放;
2、存在全局对象,该对象不断的变大,占据内存;
3、使用了c或者c++扩展,扩展内存溢出了;
初次分析的时候,先进行代码review,并没有发现存在循环引用的变量和全局对象,而且我也没有调用c或者c++扩展。所以以上三点排除。
使用memory_profiler库,将输出记录到文本中进行分析。

发现并没有内存增加的情况。
后面加上gc.collect()也没有效果。

四、二次分析
第一分析无果,然后当时没想到什么方法。只能暴力,注释一部分代码进行排查。发现注释掉将消息发送给业务服务的代码,内存不会持续增长。
怀疑是不是requests库的问题。然后进行google:requests memory leak,发现还真有这个问题。地址: https://github.com/requests/requests/issues/1685
他们的讨论非常精彩。不过上面的讨论是在pypy下,requests出现内存泄露。而我用的Cpython。而且后面的pypy版本也已经修复了这个bug。不过当时不信邪,
还是认为requests库有问题,所以改换成python3的urllib去发送信息,发现结果还是一样。然后网上一查,特么requests底层是封装url库的。已哭晕在厕所。

五、三次分析
第二次分析还是没有结果。只能看是不是有循环引用,导致内存泄露。使用objgraph查看引用和对象生成关系。
1、使用objgraph.show_most_common_types(limit=10),查看占据内存前10的对象变化关系;
一开始:


运行一段时间后:

发现没太大变化的对象。
2、使用objgraph.show_growth(),观察对象增长情况。

grep "^Message" 1.txt

发现Message对象不断的增加。
3、查看Message到底是何方妖怪。
1)使用pdb断点进行查看:

发现Message是pykafka的Message实列。
2)进行引用查看。

由于没有安装xdot,使用graphviz工具查看。

发现没有出现循环引用。
3、继续单点调试。

发现这个Message不断的变大。这时候怀疑是不是pykafka这个库导致的。然后一查。厉害了,我的哥 ,pykafka的balancedconsumer类有个参数 queued_max_messages
这个参数的意思balancedconsumer会为每个分区缓存消息,默认一个分区是2000条。如果一条消息是1M,只有一个分区的话,就缓存了2000M,对于不够内存的机器那就gg了。
至此,问题找到了。解决方法是将这个参数改小点就好了。
### 排查生产环境中内存溢出问题的命令与工具 在生产环境中排查 `java.lang.OutOfMemoryError` 问题时,可以使用多种工具和命令来分析问题的根本原因。以下是一些常用的工具和方法: #### 1. 使用 JVM 内置工具 JVM 提供了多种内置工具,可以帮助开发者诊断内存溢出问题。 - **jstat** `jstat` 是 Java 自带的一个工具,用于监控 JVM 的性能数据,包括堆内存使用情况、垃圾回收(GC)频率等。通过以下命令可以查看堆内存的使用情况: ```bash jstat -gc <pid> 1000 ``` 其中 `<pid>` 是目标 Java 进程的进程 ID,`1000` 表示每隔 1 秒刷新一次数据[^1]。 - **jmap** `jmap` 可以生成堆转储文件(heap dump),用于进一步分析内存使用情况。例如: ```bash jmap -dump:live,format=b,file=heap.hprof <pid> ``` 此命令将生成一个名为 `heap.hprof` 的堆转储文件,可以使用工具如 Eclipse MAT 或 VisualVM 分析该文件[^3]。 - **jstack** `jstack` 用于生成线程转储(thread dump),帮助分析线程的状态和行为。如果问题是由于线程过多导致的(如 `unable to create new native thread`),可以通过以下命令获取线程信息: ```bash jstack <pid> ``` #### 2. 使用第三方工具 除了 JVM 自带的工具外,还有一些功能强大的第三方工具可以帮助更高效地排查内存溢出问题。 - **Arthas** Arthas 是阿里巴巴开源的一款 Java 诊断工具,能够实时监控和分析运行中的 Java 应用程序。以下是其常用功能: - 查找占用内存最多的对象:`object histogram` - 查找问题线程:`thread` 命令可以列出所有线程及其状态,并支持查找死锁线程。 - 分析方法调用链路:`trace` 和 `watch` 命令可以跟踪方法执行路径,找出潜在的问题代码位置[^3]。 - **VisualVM** VisualVM 是一个图形化工具,集成了多个 JDK 工具的功能,如 `jstat`、`jmap` 和 `jstack`。它可以实时监控 JVM 的内存、线程和 CPU 使用情况,并支持分析堆转储文件。 - **Eclipse MAT (Memory Analyzer Tool)** Eclipse MAT 是一款专门用于分析堆转储文件的工具。它可以快速定位内存泄漏的原因,提供详细的内存使用报告和建议。 #### 3. 解决内存溢出问题的方法 根据具体的错误类型(如 `Java heap space` 或 `unable to create new native thread`),可以采取不同的解决措施。 - **调整 JVM 参数** 如果问题是由于堆内存不足引起的(`Java heap space`),可以尝试增加堆内存大小: ```bash java -Xms512m -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -jar your-application.jar ``` 其中 `-Xms` 和 `-Xmx` 分别设置初始堆大小和最大堆大小,`-XX:+HeapDumpOnOutOfMemoryError` 参数会在发生 OOM 时自动生成堆转储文件[^4]。 - **优化代码逻辑** 如果问题是由于线程泄漏或资源未释放引起的(如 `unable to create new native thread`),需要检查代码逻辑,确保线程和资源在使用完毕后被正确关闭和回收。例如,在构造方法中创建的线程应在任务完成后显式关闭[^4]。 ```python class SafeThread: def __init__(self): self.thread = threading.Thread(target=self.run_task) def run_task(self): # 执行任务 pass def start(self): self.thread.start() def join(self): self.thread.join() # 确保主线程等待子线程完成 ``` --- ####
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值