1、报错信息是:GC overhead limit exceeded。根据这个信息来看,应该是有很多对象垃圾回收失败了。
2、首先看top,cpu 到达80%,说明一直在进行Gc回收,服务器内存还是足够的,说明不是服务器内存导致的。 top 按 c 进行排序后,发现占用cpu最高的是我们的应用。在根据日志里的报错提示,基本可以断定是出现内存泄露了,应用里出现对象没有被回收。
3、然后 jps 查看对应应用的 进程id,这次我们的进程id 是 16689
4、既然是内存泄露,那就应该看一下应用现在的内存情况,jmap -heap [进程id],发现了MinHeapFreeRatio 最小堆可用比例已经为0了,MaxHeapSize 是 520 MB,NewSize 新手区就已经用了519.5 MB ,老年区 OldSize 只有 0.5MB,堆实际使用情况看不到,出现了报错。根据上面的信息基本可以猜到,是创建了过多的对象,对象没有被回收成功导致的内存泄露。
5、那就去看一下现在堆里的内存存活情况,看是哪个。 jmap -histo:live [进程id] > [文件名],之所以输出到文件里是因为对象的种类太多了,直接输出在控制台的话看不到前几名的,所以写入文件里,使用vim 查看 排名前几名。
可以发现排名第一名的是 char[] 数组,创建对象133万,占内存132 MB , 第二名是 LinkedHashMap ,创建对象 226 万,占内存 90 MB。这两个对象应用过于广泛,暂时定位不到问题,但第5,6名就有比较明确的方向了,是java 加密类的。也就是说这次内存泄露可能跟加解密有关。
不过范围还是有点太广,这个项目里加解密涉及到的地方比较多,只凭这个信息定位不到具体的类,还需要更详细一些的信息。
6、下载hprof文件到本地进行分析, jmap -dump:format=b,file=[文件名.hprof] [进程id] ,下载后传输到本地电脑。
二、本地解析
1、在本地打开JProfiler 软件进行分析,这个软件可以跟idea 的插件配合,在idea 里打开对应类的源码查看。打开我们的hprof 文件。界面就是下面这个样子,可以看到实例数还是 LinkedHashMap数量最多,其次是 char[]。
2、我们先看LinkedHashMap,选中双击之后会出现新对象集,因为我们想看的是这个类是被哪个类所引用了,所以选择 【合并的传出引用】,往下翻,发现了一个53%来自于字符串,46%来自于 java.seceurity.Provider,跟我们预计的加解密导致比较相符,再往下翻发现 46%的99%来自于org.bouncycastle.jce.provider.BouncyCastleProvider,这个眼熟,去代码里查,发现有两个工具类里有这个类,但一个工具类没有被使用。再去查这个被使用的工具类的代码,找到问题了。
这个工具类里有静态方法里每次都会新建一个 BouncyCastleProvider 对象,新对象因为在静态方法里,所以一直没有被回收,修改代码为静态代码块初始这个对象,而不是每次都创建。
修改后再次测试和查看就没有再出现内存泄露了。
修改后: