内存快照排查OOM,加密时错误方法指定provider方式错误引发的OOM

 

写在前面:java

最近开始总结内存方面的东西,已经总结之前遇到的一些内存案例分享下,接下来还有几篇,而后是进程/线程相关的,逐渐造成个人知识体系树缓存

若是你有兴趣,能够文章末尾的公众号二维码一块儿梳理这些信息。安全

 

 

OOM问题通常都是人工代码失误, 多数其实在review阶段应该能够排除,本文主要是想记录下内存快照排查OOM的一个过程 session

 

场景: 系统的交互安全彻底依赖各类加密作(作到了无session,彻底WEB无状态,这个设计之后可讲下),故加密变得很重要,但由于有新的加密引入了BouncyCastleProvider。故有修改,测试机一台机器上线一段时间后,运行一段时间后,系统变得很是缓慢,并到最后出现了OOM,最终产生内存快照文件。jvm

 

分析:ide

JVM内存分析:观察JVM内存,发现大量OU被使用没法释放引发频发GC,致使系统缓慢(基本不可用)工具

内存快照分析OOM的缘由, 对于这种内存分析最好的方法是dump两份,一份正常的,一份是发生OOM时候的进行对比测试

 

故障重现:加密

首先给1G内存给Tomcat,  当OOM时输出日志, JVM参数相关配置 以下:.net

-Xms1024m -Xmx1024m -Xmn512m -XX:PermSize=128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/log/dump/

 

正常启动后,先dump份正常的用于后面对比分析(拿OOM后产生的快照和正常的快照进行对比能够很方便找出泄漏的对象)

 

文件导入Eclipse Memory Analyzer

 

 

发现基本是Finalize占用了内存

,填个坑,为何会出现这么多java.lang.ref.Finalizer占用了内存呢

http://www.infoq.com/cn/articles/jvm-source-code-analysis-finalreference

http://knowledgebase.progress.com/articles/Article/000031452

 

 

而后开始压测,2000次请求后,系统后面发现愈来愈慢,基本不可用了,查看GC,发现基本全部内存都满了,一直在GC

 

继续说快照, 此时再把内存dump下来导入,对比

 

打开快照,从全局图查看已经很明显能找到问题,内存都被JceSecurity占用了861M,能够肯定是JceSecurity的问题。

 

进一步分析,通常用到下面3个报表

 

1, 查看全局内存

2, 内存泄漏分析

3, 大对象排序

 

这里直接用第2个,查看泄漏分析报告

 

 

96%的内存被JceSecurity这个类占用了

 

发现问题对象后,查看Details

 

 

 显示这个类的基本状况,

 

是经过GC是ROOT追溯的最短路径,这个能够追溯到问题代码的类树的结构,并找到最终引用的代码中

 

 

能够最终找到直接引用类, 这里最终存在类 javax.crypto.jceSecurity的变量, verificationResults里面

 

2, 查看verificationResults里的内容

 

说明:

with outgoning references  查看它引用了哪些内容,能够当作是 集合包含了哪些内容

with incomming references 查看引用它的类

 

 

说明:

Shallow heap   是对象的自身占用大小

Retained Heap 是对象包含内容的的总大小

 

发现内存基本被它里面的一个BouncyCastleProvider占用。

 

 

最后根据快照能得出 : 

致使问题的地方在,类javax.crypto.jceSecurity的变量verificationResults里存的org.bouncycastle.jce.provider.BouncyCastleProvider

 

 

 

 

 

代码寻找:

根据上面找到代码中有用到org.bouncycastle.jce.provider.BouncyCastleProvider的地方,发现项目中的类CryptoUtil 

工具类提炼出来;

 

 

关键是这句,每次都new了个BouncyCastleProvider

Cipher cipher = Cipher.getInstance(Constants.CRYPOTO_NAME, new BouncyCastleProvider());

 

这个类,代码来自7u40-b43 

 

从 cipher.init进去

 

 

这段用于验证提供安全的provider对象是不是JCE能够信任的JAR,并把缓存结果存起来,将把该种provider当key存入一个静态的MAP中缓存起来, 故能够理解一到OLD区也未回收.

 

verificationResults的验证

 

 

 

 

值得说明的是,若是这里是HashMap而不是IdentivHashMap的话也是不会内存泄漏的,为何这样说呢?

 

若是它这用的是HashMap, 不如new多少个BouncyCastleProvider, 在HashMap中也是一个,代码证明下:

/**
 * 小测试,用于说明下Provider与IdentityHashMap的分别特殊处理
 * @author 包子(何锦彬) 2017.01.20
 *
 */
public class Test {
    public static void main(String[] args) {
        Map hashMap=new HashMap();
        hashMap.put( new BouncyCastleProvider(), "provoder1");
        hashMap.put( new BouncyCastleProvider(), "provoder2");
        System.out.println(hashMap.size()); //输出是1
        
        Map identityMap=new IdentityHashMap();
        identityMap.put( new BouncyCastleProvider(), "provoder1");
        identityMap.put( new BouncyCastleProvider(), "provoder2");
        System.out.println(identityMap.size()); //输出是2
        
    }
}

 

 

 缘由以下:

 

1, java.security.Provider的hashCode和equals方法是有作特殊处理的,在provider中,只要密钥相等,两个Provider的比较是相等的

 

2,IdentivHashMap这个类和HashMap的主要区别是,PUT时判断两个KEY是否相等用的是==

 

IdentivHashMap代码以下:

 



而HashMap的这里是:

 

 

HashMap 里用的是 if (e.hash == hash && ((k = e.key) == key || key.equals(k))),比较的是hashCode与equals

 

解决

1.其实只要把指定方式由

Cipher cipher = Cipher.getInstance(Constants.CRYPOTO_NAME, new BouncyCastleProvider());

改为

Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

 

2, 把BouncyCastleProvider改为单例模式

 

 

 

问1:

 

至于为何要要指定用BouncyCastleProvider 

http://blog.csdn.net/defonds/article/details/42775183 

就OK,或者直接删除不指定

 

 

问2:

 其实还能够打印下存活对象, jmap -histo 7276 > dump.txt, 已经能看出些端倪

 

持续更新留言问题,解答疑问

 

欢迎关注个人公众号,重现线上各类BUG, 一块儿来构建咱们的知识体系

 

 

 或搜 “包子的实验室”

相关文章
相关标签/搜索