View on GitHub

笨狗一搏

Be yourself 勿忘初心

记一次内存泄漏以及解决办法

情景描述:

某一次版本升级后,使用top命令,发现线上服务器每一到两周会出现一次java进程的cpu使用率一直很高,甚至超过100%(为何会超过100%,因为服务器使用2核,cpu使用率是两核cpu使用率之和),内存使用率也比平时高很多,然后导致客户发出的请求一直没有响应;没有因为之前很少碰到这种问题,还是很有兴趣研究下的。

解决思路:

  1. 查看最近cpu飙升时tomcat的日志(应用服务器使用的是tomcat),打印的异常有很多类型,不过最后抛出OutOfMemoryError,据此我推测是内存不够的问题,开始有同事认为是新生代和老年代的堆分配比例不合适,需要调整,不过通过相关书籍、博客了解,这个比例正常情况是没问题的,不推荐调整,只看tomcat日志,我自己也没有绝对的把握认为是哪里有问题(那么多错误日志,也许只是表象,可能是由于一个根本原因导致日志中出现的各种错误信息,所以不能仅仅从这些错误来判断根本原因),不过,为了更好的发现问题,通过dump文件发现真正的原因,于是在线上tomcat服务器的catalina.sh文件中加入tomcat的启动参数:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=, 这两个参数表示当发生OnOutOfMemoryError错误时,会生成dump文件到指定目录。于是,我们等待下次这种错误的发生(希望越早越好,奸笑表情。。),同时也验证同事更改新生代和老年代堆的比例,是否凑效。

  2. 终于,事故还是如约在接近两个星期的时间发生了,于是,我“兴高采烈”的找运维给我导出dump文件(是不是不应该,哈哈哈哈)。得到文件后,我去,发现解压后有4G大小,于是,开始找工具开始分析了,我用的是jprofler(应该很好用,毕竟收费的)。文件很大,打开很慢,不过还是打开了,给jprofiler一个赞。然后,看到Current Object Set的Biggest Objects视图中发现有个类占用的内存特别大,如下图所示: 输入图片说明 从该图可以看到,类JceSecurity的verificationResults对象占用内存最多,占用总的内存的96%,很显然,问题应该就出在这个类的verificationResults对象上面。通过查看源码,发现JceSecurity在jdk的jce.jar包里面,于是,我试着在项目里面查找,是否哪里使用了这个类,很可惜,没找到。不过,已经看到希望了!

  3. 抱着不见黄河心不死的状态,继续使用jprofiler研究dump文件。看下线程dump的相关信息,有重大发现,如下图:输入图片说明 从该图可以看到,首先红色表示该线程属于阻塞状态,值得注意。然后我点开,查看该线程的调用信息,发现一个很熟悉的类:JceSecurity!异常兴奋,于是看到相应的调用堆栈信息,开始找我们应用相应的业务类,通过查看我们应用的代码以及相关jdk源码,真正的发现原因所在。
  4. 我们在处理一个业务时,会进行一个解密的动作,该动作会使用Cipher.getInstance方法,该方法有两个参数,由于第二个参数Provider,我们每次处理的时候,都重新new一个对象,然后,点到该方法的源码查看,会发现JceSecurity的verificationResults变量(这是一个私有的静态变量,IdentityHashMap类型)看到provider不一样,就直接调用put方法,provider作为key,而verificationResults(静态)是不会被销毁的,导致该变量越来越大,占用的内存越来越多,无法释放,这就是内存泄漏。源码如下: 静态变量 变量put key、value

  5. 解决办法: 其实很简单,只需要调用Cipher.getInstance方法时,第二个参数不用每次都new,使用静态成员变量即可。而且,根据同事回忆,这段代码确实是在之前那次版本升级加进去的,确实印证我的推断;现如今,过去一个多月,线上未发生类似事故,也说明更改的正确性!