snowater

會當凌絕頂 少壯不努力老大徒傷悲 寶劍鋒從磨礪出梅花香自苦寒來

导航

VisualVM 分析full GC问题记录

背景:JAVA APP,主要功能是处理日志并存入db

现象:运行一段时间就出现OOM问题,查看GC log发现运行没多久就一直Full GC,并且抛出OOM的异常。

[Full GC (Ergonomics) [PSYoungGen: 529920K->525999K(614912K)] [ParOldGen: 1398052K->1397869K(1398272K)] 1927972K->1923868K(2013184K), [Metaspace: 33827K->33827K(1079296K)], 4.1812153 secs] [Times: user=32.38 sys=0.07, real=4.19 secs]
[Full GC (Ergonomics) [PSYoungGen: 529920K->525483K(614912K)] [ParOldGen: 1397869K->1397848K(1398272K)] 1927789K->1923331K(2013184K), [Metaspace: 33832K->33832K(1079296K)], 5.6714054 secs] [Times: user=43.50 sys=0.09, real=5.67 secs]

GC日志中老年代非常大,而且回收效果也不明显。遇到这种问题,第一感觉还是有内存泄露,虽然Java能自己回收内存,但是不能保证我自己写的程序没有问题,比如曾经犯过一个错误往list一直add object,却没有remove,并且list也不释放,导致list越来越大,最后内存OOM。本着大胆假设,小心求证的理念,开始排查问题。

遇到内存问题,最好是希望能够直观的看到Java程序堆中现在有哪些对象,有哪些对象数目一直在递增而没有被回收。为此需要借助工具来排查了,visualVM是非常好的能满足需求的一个工具。

启动visualVM,启动程序,通过Visual GC查看内存情况。

从上图中,我们很明显发现程序的Old Generation占用空间是在不断地上涨,并且没有明显下跌,说明生成的对象都是存在内存里面的,并没有被释放掉。接下来查看一下内存中有哪些对象

使用Sampler标签页中的Memory 采集功能,查看内存中当前是哪些对象,从采样结果来看Request对象是非常多的,而且一直在递增。Request对象在程序中使用较多,接下来看下是哪个线程一直在积压Request

查看Per thread allocations,很容易看出insertRequestList线程分配的内存最多。

对照程序我恍然大悟,发现问题就出在插入数据上。程序中插入数据库采用异步批量插入方式,当生成一个Request对象就存入一个LinkedBlockingQueue,集满一定量进行批量插入。而我设置的LinkedBlockingQueue初始化了一个非常大的capacity,这就导致来不及插入的Request全部都堆积在LinkedBlockingQueue中了。

临时解决方案:将LinkedBlockingQueue的capacity设置小一些,并且采用put方式插入Request到LinkedBlockingQueue,当插入来不及的时候就等待。

修改代码后再来看下visualVM结果:

old Generation每次GC能回收大量的Object,不会出现Full GC了。

Request对象数量也并不是一直在增长了。但是InsertRequestThread线程仍然是分配了很多的内存,可以得知该线程必然还是积压了大量的Request对象的。因为只采用了临时解决方案,mysql插入数据速度上不去,积压在所难免。

 

附录:

visualVM远程监控

在执行Java 应用程序的服务器上先生成一个jstatd.all.policy

grant codebase "file:${JAVA_HOME}/lib/tools.jar" {
   permission java.security.AllPermission;
};

然后执行命令

nohup ${JAVA_HOME}/bin/jstatd -J-Djava.rmi.server.hostname=10.111.123.234 -J-Djava.security.policy=jstatd.all.policy -J-Dcom.sun.management.jmxremote.authenticate=false -J-Dcom.sun.management.jmxremote.ssl=false -J-Dcom.sun.management.jmxremote.port=8888 &  

然后在本地启动visualVM,Remote添加10.111.123.234,默认端口1099就可以监控服务器上的程序运行状况了,非常方便。

如果出现access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write")报警,则建议将$JAVA_HOME和jstatd.all.policy都使用绝对路径。

 

posted on 2017-11-22 10:54  snowater  阅读(6401)  评论(0编辑  收藏  举报