惊悚,单个 java 进程占用 700% 的 CPU
背景
最近负责的一个项目上线,运行一段时间后发现对应的进程竟然占用了 700%的 CPU,导致公司的物理服务器都不堪重负,频繁宕机。
那么,针对这类 java 进程 CPU 飙升的问题,我们一般要怎么去定位解决呢?
一、采用 top 命令定位进程
登录服务器,执行 top 命令,查看 CPU 占用情况,找到进程的 pid
很容易发现,PID 为 29706 的 java 进程的 CPU 飙升到 700%多,且一直降不下来,很显然出现了问题。
二、使用 top -Hp 命令定位线程
使用 top -Hp <pid> 命令(<pid>为 Java 进程的 id 号)查看该 Java 进程内所有线程的资源占用情况(按 shft+p 按照 cpu 占用进行排序,按 shift+m 按照内存占用进行排序)
此处按照 cpu 排序:
很容易发现,多个线程的 CPU 占用达到了 90%多。我们挑选线程号为 30309 的线程继续分析。
三、使用 jstack 命令定位代码
1.线程号转换为 16 进制
printf "%x\n" <tid> 命令(tid 指线程的 id 号)将以上 10 进制的线程号转换为 16 进制:
转换后的结果分别为 7665,由于导出的线程快照中线程的 nid 是 16 进制的,而 16 进制以 0x 开头,所以对应的 16 进制的线程号 nid 为 0x7665
2.采用 jstack 命令导出线程快照
通过使用 dk 自带命令 jstack 获取该 java 进程的线程快照并输入到文件中: jstack -l <pid> > ./jstack_result.txt 命令(<pid>为 Java 进程的 id 号)来获取线程快照结果并输入到指定文件。
3.根据线程号定位具体代码
在 jstack_result.txt 文件中根据线程好 nid 搜索对应的线程描述
根据搜索结果,判断应该是 ImageConverter.run()方法中的代码出现问题
PS
这里也可以直接采用 jstack <pid> |grep -A 200 <nid>来定位具体代码
四、分析代码解决问题
下面是 ImageConverter.run()方法中的部分核心代码。
逻辑说明:
在 while 循环中,不断读取堵塞队列 dataQueue 中的数据,如果数据为空,则执行 continue 进行下一次循环。如果不为空,则通过 poll()方法读取数据,做相关逻辑处理。
初看这段代码好像每什么问题,但是如果 dataQueue 对象长期为空的话,这里就会一直空循环,导致 CPU 飙升。
那么如果解决呢?
分析 LinkedBlockingQueue 阻塞队列的 API 发现:
//取出队列中的头部元素,如果队列为空则调用此方法的线程被阻塞等待,直到有元素能被取出,如果等待过程被中断则抛出 InterruptedException
E take() throws InterruptedException;
//取出队列中的头部元素,如果队列为空返回 null
E poll();
这两种取值的 API,显然 take 方法更时候这里的场景。
代码修改为:
重启项目后,测试发现项目运行稳定,对应项目进程的 CPU 消耗占比不到 10%。
CPU 飙升的常见原因:
1.空循环,本文中的问题其实就这个原因导致的。
2.在循环的代码逻辑中,创建大量的新对象导致频繁 GC
3.在循环的代码逻辑中进行大量无意义的计算。
简单来说,遇见 CPU 飙升的问题,就要仔细检查相关线程代码中的循环逻辑,比如 for,while 等。
总结
<font color=#999AAA >CPU 飙升问题定位的一般步骤是:
1.首先通过 top 指令查看当前占用 CPU 较高的进程 PID;
2.查看当前进程消耗资源的线程 PID: top -Hp PID
3.通过 print 命令将线程 PID 转为 16 进制,根据该 16 进制值去打印的堆栈日志内查询,查看该线程所驻留的方法位置。
4.通过 jstack 命令,查看栈信息,定位到线程对应的具体代码。
5.分析代码解决问题。
参考:
更多精彩,关注我吧。
版权声明: 本文为 InfoQ 作者【万里无云】的原创文章。
原文链接:【http://xie.infoq.cn/article/2132814e332f786fd1911609e】。文章转载请联系作者。
评论