记一次Dubbo导致的内存泄漏过程分析及解决

       近日测试团队反馈版本机测试环境请求经常卡顿,十分缓慢,甚至有超时的情况,但是请求返回、业务逻辑均是正常的,因此进行了一番排查。

        首先查看应用日志,及控制台监控,应用均表现无异常,由于版本机为单应用测试坏境,因此也排除负载均衡等问题。于是监控了一下linux环境

        1、top命令查询linux环境内存及cpu使用情况,发现应用占用cpu达到了90%多,这是不正常的,一般考虑为是否为频繁fullGC或是程序有死循环导致cpu飙高

         2、top -Hp pid | head -20命令查看该应用最占CPU的各线程情况,果然发现有一个线程占用率高达90%,其余线程均占比个位数或是0

        3、printf '%\x\n' tid  将线程ID转换成16进制,因为日志或是jstack查看时线程ID均是16进制表示的

         4、jstack -l pid |grep spid(16进制) -A  100(输出100行)获取该应用进程内相应线程的堆栈信息,发现该线程为parallelGC线程,为印证是GC导致响应变慢的猜想,在应用启动脚本中加入+PrintGCDetail 打印GC日志,发现果然是在持续的FullGC,然后用jconsole利用JMX端口连接应用,发现年老区占用接近100%,且每次GC后释放内存空间十分有限,这才导致了FullGC

         5、为解决问题,第一反应是JVM内存大小太小,GC收集器可以调整为CMS并发收集器提高响应速度。因此启动脚本中设置提高Xmx,+UseConcMarkSweepGC,并设置年老代占比大于80%后可开始年老代垃圾收集,5次收集后进行compact(CMS基于标记清除,GC后进行压缩减少内存碎片)

          6、配置完成后再查看,年老代基本大小在600M左右,占用率基本维持在75%,倒是看起来没有问题。但是此时我又去查看了一下UAT环境同一个应用的运行情况,发现他的年老代比版本机环境整整少了两三百兆,这就很奇怪了,同样的应用,并发量差不多的情况下,怎么会差别这么大呢?于是果断visualVM将应用内存dump了下来(亦可用jmap -dump)

      7、用memoryAnalyzer导入dump文件进行分析,果然有leak suspects内存泄漏分析报告,首当其冲的是一个zookeeperResigtry类内有一个concurrentHashMap高达200多M 

            8、经分析源码发现dubbo在启动时会对zookeeper上节点进行发布及订阅,会将依赖的producer注册信息缓存在内存中,并会将该信息写入到一个cache文件内,默认在用户根目录下.dubbo文件中,当订阅节点发生变化时亦会读取该文件,全量重写并重新存入内存中。若是linux系统环境下有多个应用均使用dubbo,若不单独设置则均将共用一个.cache文件,不过在写文件时是个原子操作,会生成一个.lock文件进行加锁

              9、本来这也没什么问题,正常.dubbo.cache以我们的接口量也就几十兆最多,但是我们查看版本机dubbo.cache文件时,却发现它高达数百兆,less查看文件信息,发现除了正常的服务注册信息,还有大量的空格、乱码,导致文件巨大。而由于每次zookeeper节点变化都可能导致应用对文件进行读取并重写,因此将导致应用几百兆的内存泄漏

                 10、怀疑是别的dubbo应用在一起写cache文件且应用有问题导致出现乱码、文件巨大,因此,通过阅读dubbo文档,找到了单独配置dubbo.cache文件另立路径的方法,在启动参数中加入-Ddubbo.registry.file=/tmp/dubbo/cache/dubbo.cache,为我们的cache文件单独找了一个去处。重启服务后,果然发现年老代少了200多M,整个堆稳定在了700多M,年老代使用占比45%,运行几天也没再出现过响应卡顿的问题

             可惜暂时还没查出来到底是哪个应用在写dubbo.cache文件异常导致的此问题,且先留待下次分析了

 

你可能感兴趣的:(java)