前言
内存泄漏我之前只遇到过堆内存泄漏和栈内存泄漏。matespace内存泄漏只在demo样例代码中见过。这次有幸遇到了,记录下留作纪念。
一、发现问题
在排查一个诡异的bug时突然灵光一闪想到会不会是GC导致的,于是乎执行了jstat -gc pid 1000 4这个指令
FGC 125次!!!!
之前没考虑过会出现频繁gc的情况,没有加gc日志输出,只能按照以往的习惯看看服务器整体内存情况、cpu情况、问题服务的内存情况等,发现一切正常。
最后对比dump文件
发现了一个我熟悉的对象。于是跟踪代码,优化代码,本地测试,发布代码一气呵成。
观察了一段时间发现fgc的频率降下来了,但是又来了新问题matespace空间在蹭蹭的涨。
难道我遇到了matespace内存泄漏?
尝试本地环境用同样的jvm参数启动看看能否复现问题。发现问题复现了蹭蹭的。本地能复现真实万幸啊!
Matespace是1.8以后才有的,替代了1.7的永久区。主要负责保存加载的类、方法等类相关信息。难道有类在不断的加载吗?按照这个思路继续走
加入输出类加载信息的jvm参数
-XX:+TraceClassLoading
-XX:+TraceClassUnloading
-verbose:class
)重启本地服务
发现每一段时间都有一批Script类被加载。加载后matespace的空间就会变大一点。到这里问题原因应该是找到了。但是好像又差点什么?这个Script类是个啥,我怎么找到他
二、问题分析
看到eval让我想到了ScriptEngine这个类,他是jdk1.6以后就自带的类,能够执行js代码。我前段时间刚好用了这个类,而他执行的方法名就是eval。猜测就是他了,看看我封装的工具类
代码好像没啥问题。跟下断点
每次执行完这个eval就会输出类加载的信息。问题就在这里了。继续跟,跟到了这个方法
Source对象中封装了表达式,如果表达式在缓存中有对应的Class就直接返回,否则就加载一个字节码文件到内存,缓存后返回。没错了加载类信息的地方就在这里了。如果我有大量的表达式并且都不一样的话这里就会频繁的加载类
问题已经找到了。由于我的js表达式是基于现场设备参数实时变化的,重复的概率不是很大,导致频繁的加载class文件,最终导致matespace空间不断变大。
最后在确认下,把项目中eval调用的部分注释掉,matespace空间蹭蹭的问题消失了。
该怎么解决呢。
三、解决问题
首先看看有没有配置项啥的,万一配置下就好了呢。
不死心去官网看看,结果看到了这个
头脑一热,我就想运行个简单的加减乘除表达式而已自己写一个接口不就完了。
但是马上脑海闪过一个声音
是啊要冷静下,加减乘除固然简单,但是也是有优先级的。在想想如果表达式中包含一些括号啥的也会影响到计算结果的,而且括号这个东西是可以无限嵌套的。自己写的话就解析表达式这块就能弄死自己。
那解析表达式这块会不会有现成的呢,经过各种翻翻翻果然有(IK Expression)
IK Expression是一个开源的(OpenSource),可扩展的(Extensible),基于java语言开发的一个超轻量级(Super lightweight)的公式化语言解析执行工具。
在找找发现github上已经有大佬基于IK封装好了现成的工具包。nice
引入到项目中看看效果 还不算特别丑,测试下计算结果一切正常
然后将js计算工具类底层改用ik进行处理。打包发布,问题解决。
最后献上一张调整后的战果图。我将修改前后的两个版本放到服务器上运行了一段时间(大概20个小时左右)。并输出gc情况
第一个是修改前的版本,第二个是修改后的版本,最直观的对比就是fgc少了两次。在看看MU区的情况,泄漏问题已经消失了。
总结
最开始的诡异问题依然没有解决,但是有幸遇提早发现Matespace内存泄漏也算是一种收获。