问题现象

应用运行一段时间后,突然无法正常处理请求,到Linux服务器上查看日志,发现如下大量错误:

初步定位

由OutOfMemoryError:Java heap space初步判断是JVM堆内存溢出导致。

详细定位

使用以下命令找到该应用对应的java进程的pid

ps -ef|grep java

使用jdk自带的jvm内存分析工具jmap打印出该java进程的堆内存使用情况

/usr/java/jdk1.7.0_25/bin/jmap -heap  [pid]

其中几个比较重要的参数说明

  • MaxHeapSize  最大堆内存,这里是1024m
  • NewRatio  老年代和年轻代的大小比例,这里是2:1
  • SurvivorRatio 年轻代中eden区和survior区的大小比例,这里是8:1
  • MaxPermSize  最大持久代大小

根据MaxHeapSize和NewRation参数可以发现老年代的容量(capacity)已扩展到最大(即1024m的2/3)而且无可用空间,说明大量长期存活的对象常驻内存,无法被GC回收。
使用jmap打印出堆dump文件

/usr/java/jdk1.7.0_25/bin/jmap -dump:file=heapdump.hprof [pid]

打开本地电脑的JVM可视化工具${JAVA_HOME}\bin\jvisualvm.exe,载入之前输出的dump文件然后切换到类视图

使其显示出“保留”列,并按照该列从大到小排序,有关对象大小和对象保留大小的区别,戳这里

可以确定SnfncPositiveRequestMessage类的对象占用了大量内存,然后右键该类点击“在实例视图中显示”,跳转到实例页面,在引用栏通过引用层次可以追溯到出现问题的类。

内存溢出的根因

找到内存泄漏的具体对象,就比较容易分析内存泄漏的根本原因了。

通过分析程序逻辑,发现A服务内存的Map会一直监听并缓存客户端发来的MQ请求消息,处理完成后,接着通过MQ发送给B服务处理,同时监听B服务处理完成之后的响应,A服务通过MQ接受到响应并处理后会移除Map中已缓存的对应的请求对象,并把结果发送到MQ以响应客户端。原本单机部署A服务的情况下并无问题,内存中的Map可以得到有效地释放,但是由于A服务现在分布式部署,每个A服务实例接受的请求和获得的响应并非完全一致(一个服务实例接受到的请求可能起响应可能会被另外一个服务实例处理),使得A服务内存中的Map无法得到有效释放,久而久之就导致了内存溢出。

参考

http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

https://www.ibm.com/support/knowledgecenter/SS3KLZ/com.ibm.java.diagnostics.memory.analyzer.doc/shallowretainedheap.html

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.