针对近期遇到的full gc问题的解决思路做一个笔记

针对近期遇到的full gc问题的解决思路做一个笔记

找到最近FULL GC的机器,ssh登录上去之后通过以下命令得到java的运行进程id。

ps -ef|grep java

然后打印JVM dump文件和jstack线程日志(有的服务器会对java进程进行权限隔离,也就是指定用户运行,root权限可能还无法导出dump,可通过)

sudo -u 服务账号 项目JAVA路径 -dump:format=b,file=dump文件名 java进程id

我这里执行的是

sudo -u deplpy /usr/java/jdk1.8/bin/jmap -dump:format=b,file=/tmp/log/heap.fprof 39

再将dump文件下载至本地,通过MAT分析工具打开。

打开后,可以从MAT引导的Actions里面进行相关指标查看,分别作用介绍如下:

Shallow Heap指标:对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和
Retained Heap指标:是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。
Histogram动作:列出每个类的实例数
Dominator Tree动作:列出最大的对象以及它们保存的内容
Top Consumers动作: 按照类和包分组打印花费最高的实例
Duplicate Classes动作: 检测由多个类加载器加载的类
Leak Suspects报告: 包括泄密嫌疑人和系统概述
Top Components报告:列出大于总堆的1%的组件的报告

通过Leak Suspects排查内存泄露

在Ecplise Memory Analyzer导入JVM dump文件,点击工具栏上的 Leak Suspects 菜单项来生成内存泄露分析报告,也可以直接点击饼图下方的 Reports->Leak Suspects链接来生成报告。如图:

MAT工具分析了heap dump后在界面上非常直观的展示了一个饼图,该图深色区域被怀疑有内存泄漏,可以发现整个heap才2G内存,深色区域就占了80.45%,接下来是一个简短的描述,MAT告诉我们存在两个可疑问题

Problem Suspect 1:告诉我们类加载器”sun.misc.Launcher$ExtClassLoader”占用了901,934,832 (80.45%) bytes,内存积累在”java.util.WeakHashMap$Entry”的一个实例中,由””加载,占用888,798,328 (79.28%) bytes。

通过Shortest Paths To the Accumulation Point可查看到其中”java.util.WeakHashMap$Entry”的Shallow Heap 16,400条,Retained Heap占用字节888,798,328。

通过Top Consumers定位大对象

通过鼠标左键点击”sun.misc.Launcher$ExtClassLoader @ 0x4e0800440
First 10 of 634 objects” ,弹出 选项,选择 list objects -> with incoming references

发现class jdk.nashorn.internal.scripts.JO @ 0x4e61db098下面加载的大对象关键字有”class jdk.nashorn.internal.scripts.JO @ 0x4e61db098″,到此处该问题已经有些眉目了,印象里项目中有一处java处理script的代码,剩下的就是修改代码了。

再分析第二个dump文件

通过Leak Suspects排查内存泄露

dump导出方法上面已经讲过,这里就不再赘述。直入正题,通过分析可发现几处关键字”apache”,”HashMap”。

根据Problem Suspect 1提示 可得知;

The classloader/component “org.apache.catalina.loader.WebappClassLoader @ 0x4e08000c8” occupies 45,773,960 (23.97%) bytes. The memory is accumulated in one instance of “java.util.concurrent.ConcurrentHashMap$Node[]”, loaded by “”, which occupies 37,810,784 (19.80%) bytes.

关键字:
org.apache.catalina.loader.WebappClassLoader @ 0x4e08000c8
java.util.concurrent.ConcurrentHashMap$Node[]

点击Details进入详情

查阅Accumulated Objects in Dominator Tree可发现 “org.apache.catalina.loader.WebappClassLoader @ 0x4e08000c8” 下 Shallow Heap占了 131,088 条,Retained Heap占 37,810,784字节。

继续通过鼠标左键 点击 “org.apache.catalina.loader.WebappClassLoader @ 0x4e08000c8” 弹出 选项,选择 list objects -> with outcoming references,然后点击一下 Retained Heap,进行倒叙排序,再展开”java.util.concurrent.ConcurrentHashMap @ 0x4e0e3cf00″可查看到Shallow Heap占了 131,088 条。

然后根据提供的关键字”apache”以及”hashMap”大致问题出现在了 api层,再根据调用日志定位,基本上也有眉目了。

问题分析:

1、大量jdk.nashorn.internal._和javax.script._相关实例,而jdk.nashorn.*这个包是Nashorn JavaScript引擎的包,主要是nashorn用于在JVM上以原生方式运行动态的JavaScript代码来扩展Java的功能,javax.script包用于javascript与java交互操作。于是可以基本断定这是跟javascript脚本相关操作有关,应该是有地方不断创建javascript脚本相关对象没有被回收。
2、org.apache.catalina.loader.WebappClassLoader加载了大量hashMap,也说明了一直再创建没有被回收或者说回收不及时。

解决方案:

如果JavaScript代码是固定不变,只是根据接受参数不通进行返回,那么可以尝试通过关键字 static、final,避免每次了每次调用都会new。

通过修正后,观察前后JMX指标,可看出FULL GC已经完全消失,同时新生代与老年代与之前相比明显好了许多,元数据空间占用也变得平滑 (虚线绿色 之前为 未优化指标,虚线绿色 之后 为优化后发布指标)。

优化前:

优化后:

优化前:

优化后:

优化前:

优化后:

至此FULL GC已经解决,做个笔记make一下,根据指标监控还是有优化空间,后续再按照此思路进一步对JVM优化。

后续优化效果(绿色虚线为发布时间点):

右键新窗口打开图片可放大