现象:
频繁full gc ,内存回收不掉。
分析:新增需求后发现此现象,使用阿里 的Athas查线上内存情况,
发现gc标记时间非常长。
使用sonar扫描发现很多bug,
但是经过代码分析,原因不在此。
jmap -histo pid
发现linkedHashMap有大量的数据未回收。将异常时转存的dump文件拉下来。
文件太大,只能使用profile来进行分析,漫长等待后:
这里的大对象时来自javax.crypto.JceSecurity。
????如果有使用过加解密的同事会立马发现这个是RSA或者AES中使用的实体类….原先以为的代码问题其实都不是(这个怀疑用时最长)
javax.crypto.JceSecurity导致内存溢出问题分析:
通过分析JceSecurity类源码可知调用verificationResults.put的代码只有JceSecurity.getVerificationResult方法,而调用链上游如下几个地方,都是jdk里面的,可能一下子无从查找:
回到造成内存溢出的直接原因“大量的BouncyCastleProvider实例无法回收”
再开发工具中根据关键字“new BouncyCastleProvider” 搜索相关代码发现有7处代码构造BouncyCastleProvider实例,但继续向上寻找调用链发现最终只有 DesHelper() 是有效的,并且此时的DesHelper已经是我们自定义的代码,DesHelper.encrypt 会调用Cipher.getInstance 方法,与步骤1中的调用链衔接上。
对DesHelper()上游调用链树进行分析
又可引出很多业务代码,所以需要尽量保持DesHelper的构造方法签命不变(如果对上游业务代码熟悉,改变方法签命也可以)
所以解决问题的实现方案之一可以如下:
其实这个类已经加了注解说明
/**
* Construct a new provider. This should only be required when
* using runtime registration of the provider using the
* <code>Security.addProvider()</code> mechanism.
*/
public BouncyCastleProvider()
{
super(PROVIDER_NAME, 1.59, info);
AccessController.doPrivileged(new PrivilegedAction()
{
public Object run()
{
setup();
return null;
}
});
}
最后的最后:验证正确性;
使用jmeter本地压测更改后的代码,观察内存变化和垃圾回收:
线上程序启动的时候,我们需要增加OOM的时候打印堆栈信息 ,增加
- -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof即可
小白一个,轻喷;….. 具体细节不在此文章中描述。