JVM调优 | 快速定位服务CPU过高(实战)

1,450 阅读2分钟

这是我参与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

dryout.png

3.2 定位问题

定位的核心是三个步骤

  • 找到CPU占用率比较高的进程ID
  • 在指定进程ID的进程中寻找进程CPU占用率比较高的线程ID
  • 通过线程ID去搜索打印出的进程堆栈日志,定位到具体的问题

找到CPU占用率比较高的进程ID

在终端上输入top命令

$top

top.png

可以明显的看出PID为9573的进程CPU占用率最高,我们使用htop命令会更加直观一点。

htop.png

查看进程里面线程运行的信息

我们都知道线程是处理器任务调度和执行的基本单位,一个进程下是是包含多个线程。进程粒度还是过大,不便于我们定位到具体的代码位置,我们需要找到具体是哪个线程过度使用CPU。

我们还是使用top命令。

//-H 显示线程信息,-p指定pid jstack 线程ID 
#$top -Hp 9573

PTOP.png

图上可以真正的看出,使CPU使用率暴涨的罪魁祸首是线程 9574。当然使用htop我们也能很快的定位到具体线程。

分析过滤定位问题

因为线程ID在堆栈日志中是以16进制呈现,我们先进行进制转换。

$printf %s 9574

16进制.png

然后打印堆栈日志到临时文件1.txt

# 注意是进程ID
$jstack 9573 > 1.txt

然后在文件中搜索线程所在位置

//在文件中搜索过滤并打印30条数据
$cat 1.txt | grep -A 30 2566

jstack1.png

可以清楚的看到,红框位置就是具体问题代码。

四、总结

除了使用top/htop + jstack 命令的方式,我们也可以使用JMC快速定位CPU占用率过高的问题,虽然JMC确实比前者更加简单高效,但是只能使用JMX实现远程连接,如果部署的服务没有启用JMX是用不上这个工具。

那么回到问题的根源,什么场景会导致CPU占用率过高呢?

  • 序列化和反序列(使用合理的类库)
  • 正则表达式(回溯导致,避免回溯)
  • 频繁GC,GC线程频发执行垃圾回收算法(降低GC频率)
  • 频繁 的线程上下文切换(降低切换的频率,根据业务合理建立线程池)
  • 无限while循环(尽量有限循环,即设置中断条件,让循环执行的慢一点,即Thead.yield
  • 频繁创建新对象(合理使用单例)