案例背景:
前段时间项目发布了一个V2.1.2大的版本以后,IDC机器CPU不时会突然飙升,而且是“根本停不下来”的样子,一上去了就是100%。想来也纳闷
虽然发了版本,但没有太耗CPU的功能,不应该会让CPU一下子从20%左右飙升到100%,而且是间歇性的,想想也应该是项目本身固有的bug,只不过现在访问量大了才暴露出来。
1、通过top命令看看是哪个进程当用了大量的CPU,得到pid
2、top -H -p [pid]找出此进程中CPU占用排在前头的活动线程,把pid都记录下来。
3、通过jstack -l [pid] > [pid].stack,得到线程堆栈,看看各个线程都在做什么事情。根据上面记录下来的高CPU占用线程的pid在pid.stack文件中找到对应的线程,发现这几个线程都是GC线程。
这个时候我觉得应该是有内存泄露,导致GC过于频繁,于是jmap查看内存堆栈信息,并作了一些优化,还对原先的日志处理方式作了优化。
发布,看起来有效果,可正常运行了几天了后,又有两台机的CPU给飙上去了,看起来根本问题没有得到解决。
4、又得重新来分析了! 先vmstat来看看机器的情况,发现当前的排队线程有时高达76,低时也有10个以上,已经超出了CPU数。
既然是线程问题,还是jstack -l [pid] 来看线程堆栈,这次不再草草下结论,细细看每个线程都在干啥!发现有很多线程的执行都停留在saveUserAuthDetails方法中某一行:
userAuthnBornTime是一个HashMap对象,更关键的是它是static的,也即所有对象公用,这个HashMap对象被用来保存登陆态的过期时间,很明显是读大于写,但是写也不少,在访问量大时,
大量的并发线程都需要操作这一个对象,导致很多读操作都处于忙等待状态。前面的在等待,后面还不停有请求进来,进一步加剧,因此在访问量突然增加时会直线飙升并且降不下来。
还是因此第一次太果断!
知道了原因,便得找解决方案了,了解Java的java.util.concurrent包自然会想到可以用ConcurrentHashMap来代替HashMap,以提高高并发情况下的性能,
ConcurrentHashMap不同于直接使用基于HashMap的同步操作的地方在于它内部同时保存了数据的多份拷贝,允许多个线程并发读,从而提高性能,
更多ConcurrentHashMap相关的介绍可找GOOGLE大神,相关文章太多了!
使用ConcurrentHashMap替换HashMap以后,果然机器CPU都能稳定在20%左右。