问题背景:最近开发了一个调用第三方支付平台进行代付的功能,部署到测试环境进行测试,经过压力测试发现代码无法完成功能,并且出现了响应缓慢的情况。 在发现问题后,我们观察了日志,发现请求日志打印了一半,之后就没有了,并且发现了一处其它地方的内存异常。
1 |
|
这里我们执行业务的一段代码如下:
1 |
|
此时合理的解释是程序执行到一半,发生了内存溢出,因此日志也只有一半,而且因为catch的是Exception
,OutOfMemoryError
并非是Exception
的子类,所以代码里没有找到这段业务的错误日志,而内存的不足引起了其它地方的溢出。
问题很明显,接下来我们进一步定位问题
定位问题
使用jstat工具查看gc的相关信息
- 查看gc信息
1 |
|
结果如图
- 查看gc使用率信息
1
jstat -gcutil 19010
结果如图
- 查看gc容量信息
1 |
|
结果如图
生成内存转储文件
通过这些信息,发现新生代和老年代内存的占用率均达到了99%以上,并且Full GC的次数和时间非常夸张。
由于JVM启动时没有指定HeapDumpOnOutOfMemoryError
,所以需要先手动生成内存转储文件
1 |
|
执行完成后发现dump文件竟然达到惊人的5.9G, 从服务器下载到本地进行分析
分析工具一开始用的是jvisualvm,这个工具一上来加载转储文件时就内存溢出了,还没分析别人自己先溢出了😅
1 |
|
查询了一下可以启动时指定参数如下
1 |
|
之后使用的过程中感觉加载很慢,就换了另外一个工具来分析。
使用Eclipse MAT(Memory Analyzer Tool)分析
启动时同样由于文件实在是太大使用时报错,查询了一下启动时可以如下指定Xmx
1 |
|
可以看到 javax.crypto.JceSecurity
对象占用了3.5G的空间
然后点击List objects -> withoutging references查看这个对象内部情况
可以看到属性verificationResults
占用了3.5G左右的空间
下面给verificationResults
一个特写,可以看到是内部的table数组占用的空间,而且数组大小是6420,这里只展开了25项,全部展开后可以发现
数组对象中都是org.bouncycastle.jce.provider.BouncyCastleProvider
,而且每项的空间占用591368字节,大约0.5M,共6420项,总和刚好是3.5G左右。
javax.crypto.JceSecurity
是干嘛用的呢verificationResults
字段又是做啥的,需要研究一下源码
1 |
|
通过阅读代码可以理解这段代码大致是在验证Provider的源码是否安全,验证成功将结果放进map中,key即类名,value是true或false。
这里一开始先从map中找结果,如果是已经验证成功的类会直接返回。
看到这里你一定会有一个疑问map中的key不是唯一的吗,为什么这个map有还是有6420个一个的key呢,
事实上需要注意到这里map的类型是IdentityHashMap
,这个map不同于常用的HashMap
,它判断key是否相等的条件是k1==k2
,HashMap
判断key相等的条件是
k1==k2 || k1.equals(k2)
,也就是说IdentityHashMap
判断对象相等的条件是严格的内存地址相等,同类的不同实例是不满足条件的。
至此,原因被找到了,每次传进来的都是BouncyCastleProvider
类的不同实例
定位业务逻辑代码问题
所以接下来需要定位到业务代码哪里调用的
因此我们本地启动项目进行Debug, 在JceSecurity中打断点,触发业务逻辑,程序在断点处停下来,然后可以看到调用的堆栈,可以分析代码是如何一步一步执行到JceSecurity中的。
调用堆栈信息如下图
最终发现是KeyStore.load(fiKeyFile, pass.toCharArray());
这样一行代码触发的,经过深入调查发现这里的Provider使用了BouncyCastleProvider
, 而每次获取一个新的KeyStore
实例,都会往javax.crypto.JceSecurity#verificationResults
中存放一次BouncyCastleProvider
,因此造成了内存溢出。
触发的业务逻辑代码段如下:
1 |
|
解决方法
看到一个方案是使用Security.addProvider(new BouncyCastleProvider())
使用这样一行代码,后来发现会有新的问题,
最终放弃使用BouncyCastleProvider后问题消失。
参考:
https://www.dazhuanlan.com/2020/01/30/5e322756695f7/
https://my.oschina.net/u/867417/blog/828199
https://bugs.openjdk.java.net/browse/JDK-8168469