今天运维反馈,我们线上一个web项目,CPU负载非常高,导致其他项目都快不正常了,查看线上的cpu负载,看到cpu竟然被这个进程占用了800%以上,着实惊人。
当时的CPU负载图:
而正常状态下应该是:
运维在服务器top查询的结果如下图所示:
怎么回事呢?内存占用并不高,所以排除是内存漏洞或gc等问题,最有可能的原因,就是代码上有死循环。于是试着看代码。
看了半天后,并没有发现有死循环的情况。竟然CPU看到该进程占用如此多的资源,那能否看到某个线程的使用情况呢?运维把内存dump下来以后,的确看到一些运行的线程,但看不出什么名目来。网上发现一篇好文章,《tomcat7高CPU问题》。
原文就不赘述了,这里只引用其关键部分:
第一步,使用top命令,发现占用较高的进程及ID。(上面的截图即是)
top
第二步,找到进程以后,查看进程中占用时间较长的线程信息
ps -mp PID -o THREAD,tid,time | sort -rn
下面是运行出来的截图:
第三步,根据线程信息TID,打印出线程堆栈信息,需要先得出TID的16进制表示
printf "%x\n" TID
jstack PID |grep XXX -A 30
下面是运行出来的截图:
这样就发现问题出现的具体代码了。我们的Filter中,有一个成员变量Map,这个Map会在初始化的时候,往里面put一些数据,而在put的时候,没有线程控制,那put会出现死循环吗?开始不敢相信,查看下面这些文章
http://coolshell.cn/articles/9606.html
http://xm-king.iteye.com/blog/962172
http://my.oschina.net/xianggao/blog/393990
http://coolshell.cn/articles/9606.html
总之是因为HashMap的多线程环境下,键的链表会形成环,导致后续的操作陷入死循环。解决的办法是:
1. Collections. synchronizedMap(new HashMap()) 2. concurrent包下的 ConcurrentMap 系列 ConcurrentHashMap, ConcurrentSkipListMap 3. 同步锁 synchronized 方法级 、块级 4. concurrent.locks 包下的 Lock 接口 推荐大家使用在并发的情况下使用 ConcurrentHashMap 代替HashMap