时间: 2018年3月16号晚

表现现象: 客户访问非常慢到最后无法打开

pinpoint请求: 请求逐步变慢(图1),持续Full GC&CPU占用率高(图2),出现OOM(图3

紧急措施: 加大内存、重启服务器
重现问题: 测试环境模拟并发访问(10线程&10000次/线程),开jmx端口&Virtual VM监控(图4),问题重现,启动两三分钟后开始出现GC,后续内存持续上涨,dump堆转储文件

分析问题: MAT(Memory Analyzer tool)分析,找泄露的代码

  1. 从MAT的分析可以看出, javax.crypto.JceSecurity 的实例占用了最多的堆内存(Retained Heap | 深堆) (图5

  2. 从dominated tree可以看到,javax.crypto.JceSecurity 的实例的retained heap占了73% (图6),主要是一个IdentityHashMap类型的属性verificationResults,放了很多org.bouncycastle.jce.provider.BouncyCastleProvider 对象, 每个的retained heap占用182712字节 (图7

  3. 另外从virtual vm看转储堆上的线程, 有很多BLOCKED的线程, 都卡在javax.crypto.JceSecurity.getVerificationResult(JceSecurity.java:173)图8),按现象看应该是在等待Full GC

  4. 从getVerificationResult可以看出, 只要新传入Provider都会放到verificationResults缓存起来,
    看调用链上(Rsa的decrypt 图11-> Cipher.getInstance 图12 -> JceSecurity.getVerificationResult 图13),Rsa的解密不应该每次重新new org.bouncycastle.jce.provider.BouncyCastleProvider 对象。

  5. BouncyCastleProvider的问题(图1213):

    a. provider自己是一个java.util.Properties,将所有的预设的provider的key, value都作为property put到自己的hashtable里;

    b. 如果key, value都是string的话, 还将他们放到一个java.util.LinkedHashMap.LinkedHashMap<String,String>() 的属性 legacyStrings里;

    c. 在java.security.Provider.getService(String, String) 的时候, 会把legacyStrings里所有的key,value解析成ServiceKey,Service对,放到另一个java.util.LinkedHashMap.LinkedHashMap<ServiceKey,Service>() 的属性legacyMap里

    d. 因此,每次new BouncyCastleProvider 都会产生非常多的对象和引用(占用182712字节),且缓存在JceSecurity的verificationResults没法释放。

解决问题: 按照BouncyCastleProvider 的注释,改代码,将BouncyCastleProvider实例对象作为Rsa的静态成员变量可以解决问题。

验证上线: 更改后再验证(图14),内存使用正常,CPU正常

备 注: 其实, 在重现问题,设置jmx端口,用jmeter测试的时候,已经开始在看代码, 因为最近加就只加了解密的代码,除了Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider()); 这句,其他地方看不出来会出现内存泄露,于是,顺着看下去,就看到javax.crypto.JceSecurity.getVerificationResult 里的缓存,回头看了下BouncyCastleProvider就确定怀疑正确。 然后紧急版本线上了再说。virtual vm和MAT的截图都是后续再分析时候截的。

图1
response_from_pinpoint

图2
full_gc_oom

图3
oom_of_pinpoint_trace

图4
monitor_by_virtualvm

图5
mat_analyze_summary

图6
mat_dominator_tree

图7
mat_list_objects_of_jcesecurity

图8
virtualvm_thread_blocked

图9
jcesecurity_getverifycationresult

图10
cipher_getinstance

图11
bad_code

图12
bouncycastleprovilder

图13
jcesecurity_three_map

图14
solve_the_problem