OSSClient引起的内存泄漏
最近公司的服务总是时好时坏,CPU和内存经常会占用到100%,导致服务不可用,重启之后就好了,但是过一段时间又会出现同样的情况,因此怀疑是出现了内存泄漏。没办法,只能一步步分析看问题出在哪里。
第一步
找到进程的端口号 ps -ef | grep XXX
,其中XXX代表该进程的关键字。
使用 jmap -histo:live port | head -10
初步找出占用内存最大的对象,但是由于本次泄漏找到的对象比较奇怪,因此需要更加细的来找到泄漏的对象。
第二步
使用 jmap -dump:live,format=b,file=heap.hprof port
将该进程的堆转储到文件,file的值表示转储后的名字,live是可选项,如果输入则表示只输出活的对象到文件。
第三步
将堆转储文件下载到本地电脑,利用工具进行分析。在这里我推荐 Eclipse 的 MAT 分析工具。分析完成后如下图所示:
这里重点关注 Leak Suspects, 打开后如下图所示:
MAT指出了可能存在内存泄漏的情况,点击 Details查看详情:
可以很清楚的看到存在大量的 org.apache.http.impl.conn.PoolingHttpClientConnectionManager 对象,存放在同一个ArrayList当中,并且被 com.aliyun.oss.common.comm.IdleConnectionReaper 实例持有。
第四步
分析 IdleConnectionReaper 源码:

该类继承自 Thread,且在类加载过程中就会初始化一个 ArrayList 实例。和分析报告吻合,但是当我想要查看究竟是哪里使用了这个类时却无法准确定位到。于是我想到从创建OSSClient对象开始查找。
从最初的创建OssClient对象开始查找,最后定位到

该方法,项目中使用的创建方式为 DefaultServiceClient,进入该构造方法:

由于默认配置是使用 IdleConnectionReaper 来管理过期连接,因此进入到这个方法中:

可以看到,OSS客户端专门建立了一个线程来管理其所需要的HTTP连接。而在业务中每次都会创建一个新的OSS客户端对象,那么每次也都会往该List中添加新的连接。由于这些连接对象一直被持有,所占用的内存无法释放,越到后面所创建的对象越多,占用的内存越大,一直到内存被使用完为止。
同时我可以联想到,应该有一个方法来手动释放掉已使用的连接,回到OSSClient,有一个 shutdown 方法:
将本次创建的Http连接对象从管理队列中移除,并且关闭。
总结
找到问题可能的原因以后,我在本地模拟线上不停创建OSSClient的场景,然后分析堆转储文件,MAT 给出的结论是一致的,同时在调用创建对象后再调用 shutdown 方法,内存不再不停的增长。