阿里云OSSClient使用不当引起的OOM问题分析


一、背景

最近新服务上线,运行了一段时间都很平稳,没有出现什么大的异常,突然有一天运维同事通知说注册中心上服务掉线了。于是登录了发生异常服务的组件,查看日志信息,关键信息如图:

 

 从上面两个图片可以简单了解到,

1、应该是服务发生了OOM异常
2、Consul链接因为Connection pool shut down而链接失败。

二、问题分析

猜想1:是不是因为Consul服务宕机,导致服务链接不上,并且短时间重试多次造成OOM?
实际:整个错误持续了几个小时,consul服务宕机其他服务也应该收到影响,但是其他服务并没有问题。所以猜想1应该不对。

猜想2:Connection pool shut down 这个问题是因为什么产生的?是不是可以从该地方入手,根据图中的异常位置可以查看代码。查看对应的

 继续查看


所以一定是因为 isShutDown 这个状态值变为了true,才导致打印对应的错误信息的。

继续查找shutdown可能赋值为true的地方

 

最终定位到能够调用shutdown只有finalize()或者close()。

所以猜想一定是发生OOM之后,GC在回收内存的时候调用了finalize()方法,继而引发的isShutDown变为true。

 

猜想3:我们是否可以通过dump文件,查看内存泄漏的地方,进而在本地模拟这个场景。找到运维下载OOM时候的dump文件,经过MAT工具一番查看,可以很明显的找到内存泄漏的点。

如图所示:据dump文件描述,PoolingHttpClientConnectionManager 这个对象居然占到了整个大对象内存的90%多,并且是被一个数组对象引用着的,引用的对象数量居然有60万。继续查看详细:可以看到这个对象应该是在 com.aliyun.oss.common.comm.IdleConnectionReaper 这个类当中,我们可以看下这个类的源码,可以确认应该就是下面这个静态对象列表。

 

 

查看该列表使用的地方,会发现在registerConnectionManager方法内进行填充

registerConnectionManager方法调用的地方,

 最终定位,果然是在创建OSSClient的时候调用的

OssClient构造函数

我们看下OssClient是如何创建的,为什么connectionManagers列表会持有这么多对象。最终发现在上传图片的过程中,我们每调用一次getOSSClient()就会生成一个OssClient对象,就会在connectionManagers添加一个PoolingHttpClientConnectionManager对象。

最终问题定位应该就是这里,因为没有及时关闭OssClient导致的内存泄漏,因为不是一瞬间就造成内存暴涨,而是缓慢的增加,所以没有第一时间发现,这也是为什么重启之后就没有报错的原因。

 

三、问题重现
我们本地启动服务,修改一下jvm参数如下(比线上减少了10倍)
-Xms200M -Xmx200M -Xss256K

顺便增加一些测试代码,打印一下当前jvm的内存信息

启动程序,模拟线上上传图片:

随着请求数量增加,可以发现connectionManagers中PoolingHttpClientConnectionManager对象也在不断上涨。

 加大请求次数,通过VirtualGC查看内存分布,也可以发现 老年代一直在上涨,几次GC之后也没有明显下降

最终在我们还可以看到

1、出现了OOM异常

2、Consul的 Connection pool shut down 也被触发了

综上所有的迹象都说明就是因为OSSClient这个类被一直new,导致内存不断泄漏、最终导致程序OOM使得出现了很多异常。

 

四、问题解决

因为本服务需要提供用户上传图片到阿里云OSS的功能,并且调用频次还算比较高,所以解决方式有两种。
1、每次new OSSClient之后,需要调用shutdown方法,移除PoolingHttpClientConnectionManager对象。

 

 

我们都知道new操作是一个比较耗费资源和性能的操作,而且什么时候shutdown不好控制,所以我觉得这样的方式比较适用于操作比较少的场合。

2、使用单例的OSSClient,看了刚刚的代码OssClient本身也封装了一个PoolingHttpClientConnectionManager对象,说明它本身就是支持链接池的,也就是可以并发访问。

修正为单例之后,执行测试:

可以看到经过一段时间的请求,整体老年代内存维持不高。

四、问题总结

经过这个bug的排查,可以有以下几点收获:
1、遇到OOM问题,仅凭借日志输出可能不能马上定位到问题的本质,最好可以通过dump文件进行分析
2、像HttpClient这种需要建立socket链接的资源,在使用的时候尽量能够池化,避免大量创建对象耗费资源,并且使用后要考虑关闭的问题。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM