一、背景
最近项目上线,刚上线初期项目使用量不多还算稳定。后续随着项目使用量的增加,每过几天项目会宕机一次。刚开始以为是线上服务分配内存较少导致,于是给加大了jvm堆内存的大小。随着项目的稳定以及市场部门的推广项目使用量逐渐增加,宕机时间再次出现。
二、过程
于是,初步猜测是由于有地方发生内存泄露所导致的。于是让服务带着-XX:+HeapDumpOnOutOfMeory -XX:HeapDumpPath=path(存储堆转储文件的路径)/heapdump.hprof两个参数运行(第一个参数表示在发生内存溢出时保存堆转储文件,第二个参数表示保存堆转储文件的地址)。没过几天不出所料,项目再次发生oom成功拿到堆转储文件。
三、堆转储文件的分析。
我使用的是MAT(Memory Analyzer Tool)工具进行分析,当然也可以使用jdk自带的jconsle、jvisulvm等工具进行分析(其实差不多)。具体分析步骤如下:
1.首先通过mat导出hprof文件(第二个图片上的选项是选择是否生成一些报告,暂时不需要直接点cancel)
2、我们可以从饼图中占用内存较多的对象入手(查看占用内存较大的类被谁引用)
3、通过上边的分析我们可以定位到占用内存最大的是verificationResults这个IdentityHashMap。下边就可以找到这块代码,从代码入手查看为什么会出现内存泄露了。找到这个类我们发现当前类是一个常量map,他的生命周期和类一样长,jvm的gc回收不掉他。
4、继续往下看可以发现这个map里存储的是我们自己代码里创建的BouncyCastleProvider实例。当前是在方法内部每加密一次都会创建一个该对象,如果使用量上来会导致占用内存过多,进而引发线上oom。
Cipher cipher = Cipher.getInstance("RSA", new BouncyCastleProvider());
四、如何解决。
通过上边的分析我们可以知道导致内存泄露是由于频繁的创建BouncyCastleProvider实例,而且是将它放入一个静态的map中导致。那么如何解决该问题呢?首先既然是由于过多的创建BouncyCastleProvider实例,那么我么可以不在方法里创建把这个实例改成静态的类属性。
private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider();
其次我们也可以通过单例模式只创建这个类的一个实例。
五、总结
到此为止这个问题算是完美解决。由于正近正在学习周志明老师的《深入理解java虚拟机》这本书,看书的时候觉得这些知识非常枯燥,但是当遇到问题的时候发现熟练掌握虚拟机相关知识的重要性。