问题描述

云平台虚拟机硬盘操作失败

排查

通过日志排查是文件打开数目过多的原因

利用lsof查看各个进程文件打开数目,发现ProxyServer连接数目达到几千。于是对此进行优化:

优化数据库连接数目

数据库采用的hibernate进行连接,设置最大连接数目也就是50,怎么实际使用会超过200呢(通过mysql查看processlist进程)

分析了代码怀疑是数据库连接初始化的原因。因为这个项目并没有采用spring管理jpa,所以就数据库事物管理器是自己管理,很可能会出现多线程下多次初始化,通过在初始化代码前增加日志,发现确实一瞬间初始化次数到达4次。。。解决方案很简单,增加同步块即可

修改后发现DBProxy和数据库的连接数目已经和配置文件的连接数目一致了。但是一段时间后DBProxy连接数目还是很大。

统计发现占用情况如上,volumeScheduler怎么占用这么多连接。大部分连接集中在DBProxy和VolumeSchedulerServer之间的连接中。

一时间很费解,DBProxy和许多模块都有连接,怎么可能都集中在VolumeSchedulerServer上呢,要出错的话,很多更常用的模块(如VMSchedulerServer)显然访问数据库的请求更多。此事因为一时间没有头绪,那段时间任务也比较多就一直放着,只是简单修改了linux对单线程和用户打开文件数目的限制(由1000提升到5000)临时应急。后来看到了一些分析内存溢出排查的博客,觉得可以借此机会好好试试。

避免连接未释放导致的内存溢出

先是用了jstack分析线程情况,线程数目还是比较合理,状态也比较正常。
随后使用jmap分析java内存使用情况,通过jmap分析前后两个相距较长时间的内存使用情况,发现有几个类数目增加比较块,而且数目越来越多没有释放掉。最为关键的是这些类的数目和 VolumeSchedulerServer同DBProxy连接数目的数量级基本相同,基本锁定就是这些类导致的异常。

图上几个地址类为重点观察对象。看到生成的这么多对象,怎么生成的/为什么没有释放掉等问题很自然浮现在眼前。看来还需要借助工具更进一步的分析。

  1. 先打印heap信息
    jmap -dump:live,format=b,file=heap.bin

  2. 使用MAT(Memory Analyzer Tool)导入上述文件进行分析
    在Leak Suspect页面可以查看可能存在的内存泄漏的原因

在Histogram视图中可以看到内存中的对象以及对象的个数和大小。

通过对类引用之间的层层分析,最后发现根源是这个类产生过多

结合代码发现不应该会生成这么多的类,因为TipClient只能由ClientFactory产生(tipClient的构造方法是protected),而ClientFactory方法调用只有在生成TipRPCClient采用到

可是TipRPCClient个数不等于TipClient个数,TipRPCClient个数很正常,刚好等于八个代理类的数目

感觉很奇怪,没辙,准备增加日志代码进行调试。
在ClientFactory生成client处添加接口调用次数的统计分析

重新打包上传程序,发现多出的TipClient确实是通过这里调用的

结合日志上下文的信息,发现后续每次产生的TipClient,之前都有KeepAlive日志。遂觉得这次调用可能源于KeepAlive

通过代码分析,发现这里确实有调用TipClient

这里的问题在于这个TipRPCClient没有重用不说,使完还没有释放,也就说这个工厂方法本身就有问题,因此责任不能全部归咎于上层的使用者。将调用方法改为如下的单例模式,问题得到解决。

最后还遗留一个问题,就是TipClient和TipRPCClient数目为什么不一致,原因就是TipRPCClient在使用上述不合理的方法创建后变量就失去引用,被垃圾回收了,而其中的子变量因为和其他一些变量想关联因此没有被释放,简单从类引用关系也可以看出来

TipClient被ChannelCloseListener所引用,因此没有被垃圾回收。ChannelCloseListener是当Channel关闭后触发的监听器,TipClient没有关闭,其上的Channel也就不会关闭。

更多推荐

文件打开数目过多——bug排查和修复