记一次内存溢出的问题处理过程
概述
OOM是每个Java程序员都可能遇到的问题,但很多问题可能其本身并没有考虑好,或者是采用了一种类似于简单增大内存的处理机制,这种可能部分上能解决问题,但要是具体要原因,那可能大部分程序员都不会很清楚。
本篇文章以自己的实际工作为切入点,从一次内存溢出的处理过程来简要说明下如何处理。
需求
系统非常复杂,由于工作原因不好说明具体是什么,将其处理过程简单抽象一下,大概如下:
其中队列1用于暂时缓存MQ的消息,防止积压;队列2用于业务逻辑处理,将队列1中的数据处理为一种合理的数据结构放入队列3,在队列3中该数据结构即合理的。队列3的处理是最终的逻辑处理过程,之前的那几种都是为其服务。
工具
主要的使用工具就是JProfile,我使用的是9.x版本,网上找的可用激活码。
现象
这个问题的来源其实并非做性能分析,而是做大数据量处理。项目结项的要求是可以达到亿级请求处理,因此就做了一个长期监测,这一监测发现内存的问题非常严重,经常跑一会儿(半个小时)系统就因为内存问题卡住不动。
于是把内存设置为256M(最大最小均为256M),通过JProfile启动程序进行分析:
从上图可以发现两点:
- 1、256M的内存是可用的,前5分钟的正常业务逻辑很显然说明了这点;
- 2、5min时候GC失败,意味着没有可GC的资源,那么就存在两种可能,一种是对象不断增加,并且不需要释放,另一种则是存在内存泄漏。
分析
猜测是不可能完成这项工作的,当然必然的推理是有必要的,下面首先说明一下JProfile对这种问题的处理思路。
- 1、找内存规律;内存一直变大没有释放那么这部分对象占用的内存是比较大的,并且内存占有量基本不会下降;
- 2、找源头;根据1找到这些对象之后,需要再找到这些对象的来源,也就是产生他们的地方,这才是我们最终要处理的那部分逻辑;
- 3、分析代码改改改。 最后肯定是脱离不了分析代码修改这个阶段,那就需要根据2的提示,完成这一目标。
1)找内存规律
打开Live memory 的All Objects,如下图所示:
我们可以从图中看到最右侧那个占有量最大的几个(按照size排序),一般就是我们需要分析的对象,像图中的byte[]这个就是需要分析的,但是这种对象不同于其他的对象,其他的对象我们可以通过类名就能简单分析出大概是什么对象,但byte[]这种就稍微复杂一点。
2)找源头
右键选中该对象,然后点击“Show Selection In Heap Walker”这个按钮,我们就可以进入这个对象的专属分析界面:
专属分析界面如下:
这个时候我们再选中该对象,点击那个“Selected Instances”按钮
点击弹窗中的Allocations单选按钮,选择Allocation tree,这个意思是调用树。也就是整个调用到该对象的过程。
这个图片可能很难分析,需要将他们点开,找到那个调用占比最高的那部分代码:
这样我们就发现其实是rabbitmq的readFrom方法搞的鬼。
3)分析代码改改改
为什么会是rabbitmq的问题?
我们的队列1使用的是rabbitmq,采用的方式就是每次消费的消息放入队列中, 该队列使用的是LinkedBlockedQueue,这个队列很明显是不限长的。
经过分析发现是需要改动的点还比较多:
- 1、rabbitmq未设置Qos,导致消费过快,(rabbitmq默认是以最快的方式发送给消费者);
- 2、队列1和队列2都采用了未限长的队列,这两个队列需要协调,因此都需要做调整;
- 3、队列3使用的是disruptor,后来了解到时因为其RingBuffer的长度导致;
结果
最终经过调整,把三级队列修改为两级,全部采用Disruptor来实现,如下:
最终的效果图变成了如下这样:
也是在256M的情况下结果,堪称完美~~~~~~~
欢迎跟帖交流~~~~~