theme: geek-black
这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战
接着上一篇 【JVM调优 】 快速定位服务CPU过高(理论)|8月更文挑战
三、实战
3.1 实战之前准备
先写一个简单的榨干CPU的例子CpuDryOutExample.java
,内容相当的easy,就是在无中断条件的循环语句中,不断地执行打印内容操作。
写个例子
public class CpuDryOutExample { public static void dryOut() { while (true) { System.out.println("dry out utilization rate of cpu"); //do something } } public static void main(String[] args) { CpuDryOutExample.dryOut(); } }
编译
$javac CpuDryOutExample.java
输出CpuDryOutExample.class
,注意最好去掉java文件里面的package,否则编译过程中很有可能会报找不到main方法,当然你编译时特殊处理也行。
运行
将CpuDryOutExample.class
上传到Centos 7
服务器上,该服务器已经预先配置好了Java运行环境(JRE),然后执行下面命令运行程序。
$java CpuDryOutExample
此时屏幕疯狂输入dry out utilization rate
3.2 定位问题
定位的核心是三个步骤
- 找到CPU占用率比较高的进程ID
- 在指定进程ID的进程中寻找进程CPU占用率比较高的线程ID
- 通过线程ID去搜索打印出的进程堆栈日志,定位到具体的问题
找到CPU占用率比较高的进程ID
在终端上输入top
命令
$top
可以明显的看出PID为9573的进程CPU占用率最高,我们使用htop
命令会更加直观一点。
查看进程里面线程运行的信息
我们都知道线程是处理器任务调度和执行的基本单位,一个进程下是是包含多个线程。进程粒度还是过大,不便于我们定位到具体的代码位置,我们需要找到具体是哪个线程过度使用CPU。
我们还是使用top
命令。
``` //-H 显示线程信息,-p指定pid jstack 线程ID
$top -Hp 9573
```
图上可以真正的看出,使CPU使用率暴涨的罪魁祸首是线程 9574。当然使用htop
我们也能很快的定位到具体线程。
分析过滤定位问题
因为线程ID在堆栈日志中是以16进制呈现,我们先进行进制转换。
$printf %s 9574
然后打印堆栈日志到临时文件1.txt
```
注意是进程ID
$jstack 9573 > 1.txt ```
然后在文件中搜索线程所在位置
//在文件中搜索过滤并打印30条数据 $cat 1.txt | grep -A 30 2566
可以清楚的看到,红框位置就是具体问题代码。
四、总结
除了使用top/htop + jstack
命令的方式,我们也可以使用JMC
快速定位CPU占用率过高的问题,虽然JMC
确实比前者更加简单高效,但是只能使用JMX实现远程连接,如果部署的服务没有启用JMX是用不上这个工具。
那么回到问题的根源,什么场景会导致CPU占用率过高呢?
- 序列化和反序列(使用合理的类库)
- 正则表达式(回溯导致,避免回溯)
- 频繁GC,GC线程频发执行垃圾回收算法(降低GC频率)
- 频繁 的线程上下文切换(降低切换的频率,根据业务合理建立线程池)
- 无限while循环(尽量有限循环,即设置中断条件,让循环执行的慢一点,即
Thead.yield
) - 频繁创建新对象(合理使用单例)