问题描述:
(2015-06)权限系统每运行一段时间,cpu的使用率高居不下,按道理,4个核的CPU,即使线程经常100%是没问题的,毕竟没到400%地满载,但是对于一个并发量不高的权限系统,这往往有潜在问题。
Top:
f 或者F 从当前显示中添加或者删除项目。
o 或者O 改变显示项目的顺序。
l 切换显示平均负载和启动时间信息。
m 切换显示内存信息。
t 切换显示进程和CPU状态信息。
c 切换显示命令名称和完整命令行。
M 根据驻留内存大小进行排序。
P 根据CPU使用百分比大小进行排序。
T 根据时间/累计时间进行排序。
1、使用top命令,然后按c键,根据command的详细信息找出对应的java进程PID(ps、jps等方法也是OK的,方法可谓五花八门了),这里是2965;
2、top -p 2965,然后再按H(H是显示当前进程中的各线程),找到占cpu为100%左右的某线程(线程的PID为:2970)
3、将2970转16进制:B9A(快捷键win+r-->输入calc-->使用程序员计算器)
4、使用jstack -l 2965 | grep B9A找到当前线程,结果是:"Concurrent Mark-Sweep GC Thread" prio=10 tid=0x0000000053293800 nid=0x*** runnable
5、而Concurrent Mark-Sweep GC Thread,是JVM的标记清除线程,可见GC太频繁,由于JVM中根据对象的存活周期的不同将内存划分为几块。把java堆分为新生代和老年代,根据各个年代的特点采用适当的收集算法。在新生代,每次垃圾收集时都发现有大批对象死去,只有少量幸存者,选用复制算法从from区到to区,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、并没有额外空间对他进行分配担保,就使用“标记-整理”算法进行回收,本次的内存漏问题主要发生在老龄代。
6、为了确定一下以上推测,再使用 jmap -heap 2965(注意:jmap不是运行分析工具,在生成结果时JVM可能会挂起,因此当生成统计结果时需要确认这种暂停对程序是可接受的),查看得老龄区占用内存已达99.99%(而老龄区的内存设置为: capacity = 2147483648 (2048.0MB));使用jmap -dump:format=b,file=dump.65 2965将内存dump成文件,将内存文件拿下来分析;
(可以不拿下来,直同样直接可以在服务器上使用jhat对dump文件分析,jhat默认会打开7000端口的web服务;并具如果dump文件过大,jhat需要使用的xmx参数也需要注意设置,否则OOM;jhat -J-mx4G -port 8080 D:\klive\midea\wspace\document\data\dump.65,但注意端口是否开放,是否冲突,机器是否够内存,详细分析过程不写了)
为了使结果更明显借用第三方工具:
7、下载memory analyzer tool工具进行dump文件分析,由于本人使的是intelij idea,而这个idea没有相对应的MAT插件,就懒得下载mat对应的eclipse的插件而是下载具有能独立运行的mat版本,具体地址是http://www.eclipse.org/mat/downloads.php,具体的eclipse插件可参考:http://blog.csdn.net/yanghongchang_/article/details/7711911
8、由于dump下来的文件大约3.2G,而想下载到工作的电脑上,这个是文件是大了点,使用tar -czf dum.tar.gz dump.65命令进行压缩,(加z是tar调用gzip进行压缩),具体的压缩命令,请参考:http://www.jb51.net/LINUXjishu/43356.html结果为600多M,虽然还很大,但却小了很多。【bzip2是一个压缩能力更强的压缩程序,.bz2结尾的文件就是bzip2压缩的结果。与bzip2相对的解压程序是bunzip2。tar中使用-j这个参数来调用gzip。下面来举例说明一下:# tar -cjf all.tar.bz2 *.jpg】
9、由于dump文件过大,所要以需要修改MAT启动的xmx参数:到mat安装的根目录下找到:MemoryAnalyzer.ini,修改-Xmx的值4096m;
image.png10、启动mat,加载dump文件进行结果分析,不看不知道,一看挺恐怖javax.crypto.SunJCE_b这个类竟然占了2.1G内存;
image.png image.png
结果一目了然:
org.bouncycastle.jce.provider.BouncyCastleProvider()的实例,一直回收不了,快撑爆了2G的老龄区;
后来分析知道的这个是用户解密的代码问题。检查common包的RSAUtil类,结果也是一目了然,这代码估计也是copy网上的然后没仔细检查一下,二话没说地使用了。
[图片上传失败...(image-41fd49-1597917382142)]
总结:这问题告诉我们这种结果不是我们想要;
备注:更详细的MAT分析:http://seanhe.iteye.com/blog/898277
/*******************************我是分割线************************************/
二、RSA加解密BC类导致的内存溢出(2015-08-28)
1.事由:权限模块长时间运行会出现CPU100%的情况,用JVM内存泄漏分析得出是登录的时候会对用户密码进行解密的方法导致的。
2.事发地点:Cipher cipher = Cipher.getInstance("RSA",new org.bouncycastle.jce.provider.BouncyCastleProvider());
3.分析:
解密用到的org.bouncycastle.jce.provider.BouncyCastleProvider 补充:Bouncy Castle 是一种用于 Java 平台的开放源码的轻量级密码术包,负责密码术算法。
看一下BC的源码,在这个问题中,每一次解密都new 一个bc,而new bc时会执行setup的方法,这个方法就是负责把密码术的算法初始化并放到罪魁祸首linkMap里面。
image.png
虽然解密的时候都是new 一个bc, 但为什么没有被GC呢?下一步看 Cipher这个引擎类在getInstance(String,Provider)这个方法里做了什么。
image.png可以看到实例化的时候都会调用JceSecurity校验BC这个provider是否存在,由于我们是每一次解密都new一个bc,所以会一只往面放值。verifyingProviders和verificationResults这两个map里
image.png看verifyingProviders和verificationResults的定义
image.png由于是final+static不在gc之列,所以权限模块一直没有回收这两个map,最终导致LinkMap的爆满
网友评论