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

问题描述

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

排查

通过日志排查是文件打开数目过多的原因
文件打开数目过多——bug排查和修复_第1张图片
利用lsof查看各个进程文件打开数目,发现ProxyServer连接数目达到几千。于是对此进行优化:

优化数据库连接数目

数据库采用的hibernate进行连接,设置最大连接数目也就是50,怎么实际使用会超过200呢(通过mysql查看processlist进程)
文件打开数目过多——bug排查和修复_第2张图片
分析了代码怀疑是数据库连接初始化的原因。因为这个项目并没有采用spring管理jpa,所以就数据库事物管理器是自己管理,很可能会出现多线程下多次初始化,通过在初始化代码前增加日志,发现确实一瞬间初始化次数到达4次。。。解决方案很简单,增加同步块即可
文件打开数目过多——bug排查和修复_第3张图片
修改后发现DBProxy和数据库的连接数目已经和配置文件的连接数目一致了。但是一段时间后DBProxy连接数目还是很大。
文件打开数目过多——bug排查和修复_第4张图片

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

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

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

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

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

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

  2. 使用MAT(Memory Analyzer Tool)导入上述文件进行分析
    在Leak Suspect页面可以查看可能存在的内存泄漏的原因
    文件打开数目过多——bug排查和修复_第6张图片

在Histogram视图中可以看到内存中的对象以及对象的个数和大小。
文件打开数目过多——bug排查和修复_第7张图片

通过对类引用之间的层层分析,最后发现根源是这个类产生过多
文件打开数目过多——bug排查和修复_第8张图片

文件打开数目过多——bug排查和修复_第9张图片

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

文件打开数目过多——bug排查和修复_第10张图片

可是TipRPCClient个数不等于TipClient个数,TipRPCClient个数很正常,刚好等于八个代理类的数目
这里写图片描述

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

文件打开数目过多——bug排查和修复_第11张图片

重新打包上传程序,发现多出的TipClient确实是通过这里调用的
文件打开数目过多——bug排查和修复_第12张图片

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

通过代码分析,发现这里确实有调用TipClient
文件打开数目过多——bug排查和修复_第13张图片

文件打开数目过多——bug排查和修复_第14张图片

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

文件打开数目过多——bug排查和修复_第15张图片

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

文件打开数目过多——bug排查和修复_第16张图片

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

你可能感兴趣的:(java)