今天看JVM群里有人发了一个GC情况,让人帮忙看优化的,于是我也凑热闹发了出来想让群里的大神们指导优化一下,以下是优化过程记录.
一开始我贴了下面的两张图
jstat看GC记录jstat -gcutil pid 1000 20
jcmd看VM参数(第一次使用这个命令)jcmd pid VM.flags
可以看到YGC了8W多次,FGC有1100+,相比较另一个发出来求教的,我这个更糟糕,他的是运行了3天左右 FGC370次
然后飞神让我看下运行时间ps -p pid -o etime
我的也是跑了3天左右,感觉优化空间非常的大
又让我拉了JVM配置jinfo -flags pid
(没权限,没执行成功)ps aux | grep pid
发现我的JVM完全没做过优化,据我自己的印象,就改过PermSize,因为这个OOM过,所以调大了一点。
然后飞神给了我一份他之前用过的配置JAVA_OPTS="-Xms2g -Xmx2g -Xmn512m -XX:MaxPermSize=256m -server -Xss256k -XX:PermSize=128M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/data/log/gclog/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/log/jvmdump/jvm.bin -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:+TieredCompilation -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+PrintHeapAtGC
并嘱咐了一句loggc和dumpPath提前mkdir
因为已经是周五晚上了,我没有权限直接修改这个配置,所以准备下周一再配上去看效果。
万万没想到,回家路上,笨神出来说话了,要我看下存活实例
jmap -histo:live pid
由于没有开启GC日志,于是笨神让我开着jstat(飞神提到jstat -gccause pid
可以跟踪gc情况),然后在另一个窗口执行jmap -histo:live
刚开始没明白,后来才知道原来这个命令可以触发Full GC
可以看到执行了Full GC以后Old区从90%降到了79%,FGC效果很差,说明活对象太多了。
回过头去看jmap实例,发现AtomicInteger
这个类对象特别的多,竟然有300多万个实例,已经是top2了。
翻看代码没有发现有使用这个类的地方,初步怀疑是依赖的jar包使用的,笨神建议dump用MAT分析一下。
dump命令导出文件jmap -dump:format=b,file=pid.dump pid
检查了一下项目代码后,发现了问题,项目代码就不贴了
项目中有一个统计API调用次数的类使用了AtomicInteger
,在这个类里针对每个用户都会生成大概六七十个AtomicInteger
实例,每次上报过数据之后只是简单的把值设置为0,导致负责统计的实例一直持有这些AtomicInteger
,而且随着新用户的不断增加,这些实例数量还会持续增长,最终会导致内存溢出。
修改完这个BUG,重新上线后,跑了一段时间查看gc情况
可以看到比之前好一些了 但是FGC的次数还是比较多,照这情况下去一天的FGC估计会有200+,这当然是不可接受的(前面说的另一个人370次FGC,飞神说如果是跑了半年的话还可以接受)。
看了飞神推荐的阿里毕玄大师的文章为什么不建议
于是准备先不上CMS GC,就简单的把Xms,Xmn和GC日志配置了一下
-Xms2048m -Xmx2048m -XX:PermSize=128m -XX:MaxPermSize=128m -Xss256k -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/data/project/delivery_v9/code/logs/gc.log -XX:GCLogFileSize=50m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/project/delivery_v9/tomcat/jvmdump/jvm.dump
运行了一天左右看下对比结果
未配置
已配置
可以看到配置完以后对GC影响还是挺大的(不管是YGC还是FGC),当然这也是必然的,毕竟没有配置的机器初始内存比较小,在不断扩容的过程中会频繁的GC,而且这个时候其实没配置的那台机器内存还没有扩充到上限,在资源充足的情况下,这种动态扩容显然是完全没有必要的。
配置完的机器虽然GC时间和次数已经降了很多了,但是还是没达到期望的结果,考虑到这个程序短时间的活对象是比较多的,可以通过调整年轻代和老年代的内存占比来减少因为年轻代内存不足导致晋升到老年代的对象。
现在已经离开这个项目了,所以后面就没有再继续优化了,以后再有这方面的实践重新写文章记录。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。