一、 问题定位篇

1.具体步骤

  1. 定位内存泄漏类型

II. 定位发生泄漏的具体方法

III. 定位使用泄漏方法的具体堆栈

2.实操过程

压测,发现后台程序占用内存超过80%,8G内存占用超过6G,如图4.4所示。遂怀疑内存存在泄漏。

 

图4.4 压测一段时间后内存占用图示

         然后使用MAT对进程dump堆栈进行查看,发现堆栈大小实际只有不到1G,如图4.5,那么剩余5G的内存就是堆外内存,因此怀疑堆外内存泄漏。

 

图4.5 压测一段时间后内存占用MAT图示

严谨起见,再次压测并记录起点和终点的dump堆栈,使用MAT在两个时间点dump堆栈进行对比,发现堆栈变化较小,如图4.6所示。因此判断是堆外内存泄漏。这一点在后来使用jconsole监控jvm时也得到验证,如图4.7所示,20分钟非堆内存使用就上升了600M。

 

图4.6 两个压测节点间的内存使用对比MAT图示

 

图4.7压测过程中jconsole非堆内存使用图示

然后使用google的perftool进行一个压测过程中内存使用的抽样,发现Java_java_util_zip_Inflater存在内存泄漏,如图4.8所示,该线程使用330M内存,Java_java_util_zip_Inflater就使用了120M。

 

图4.8 perftool抽样日志单线程分析图示

继续对比其他线程后发现。随着线程内存使用的变多,Java_java_util_zip_Inflater占用内存的比例上升明显。因此判断是该方法发生堆外内存泄漏。然后再检查MAT的堆栈对象数量,发现当前占用内存最多的对象是jboss的ClassLoader,继续检查发现最后指向的是cglib的类加载过程,如图4.8所示。

 

图4.9 MAT对象树大对象分析

继续在MAT中使用OQL对java.Util.zip方法进行分析,发现该对象的引用树确实是在ClassLoad上,如图4.10所示。

 

图4.9 MAT对象树大对象分析

最后编写Btrace脚本,如图4.10所示。使用Btrace定位具体使用到Java_java_util_zip_Inflater方法,如图4.11所示,同时堆栈中也有cglib的调用堆栈,定位行内的方法为TValidateUtil。

 

图4.10 Btrace脚本

 

图4.11 Btrace调用堆栈分析

后续在JDK官网发现相同问题未修复,原因是该类会调用本地native类,分配C的堆内存,因此必须调用end方法,否则C的堆内存不释放,会持续增长。同开发确认,该方法为一些校验内容不影响业务,因此先注释掉该方法验证是否堆外内存泄漏有所减轻,测试结果如图4.12所示,在相同时间内同并发压测同一接口情况下,内存泄漏明显减轻。

图4.12 jconsole堆外内存监控对比图

  1. 由于该部分代码较多,修改起来较为费时,且和业务沟通不能单纯通过注释掉改方法解决。因此需要采用如前所述规避方案。