以下操作基于Linux系统

top + jstack

演示

# 命令行输入top命令
top

image.png
可以看到3878进程号的CPU在30%以上

# 使用printf %x 3878 得到 十六进制的 f26(记录下来)
printf %x 3878

image.png

# 使用 top -Hp 3878 命令查看3878进程号里面线程的运行信息
top -Hp 3878

image.png
这里可以看出3888这个线程占用CPU高达39%,其他线程占用率都是正常的

# 使用printf %x 3888 得到 十六进制的 f30(记录下来)
printf %x 3888

image.png

# 使用jstack dump一下CPU过高的进程(3878),
# 并将结果输出到当前目录的1.txt文件
jstack 3878 > 1.txt
# 使用以下命令在1.txt文件里面搜索包含f30的这一行,且往后搜索30行
cat 1.txt | grep -A 30 f30

image.png
由结果可以看出有问题的线程是Thread-0(1),有问题的代码在 at HoldCPUMain$HoldCPUTask.run(HoldCPUMain.java:9)(2) ,找到有问题的代码后就可以定位问题了
image.png
这里的代码HoldCPUTask(1)有一个循环,一直在做大量的计算占用了CPU资源,而代码LazyTask只是空闲线程,不会占用CPU资源

附上详细代码

/**
 * 该测试简单占用cpu,4个用户线程,一个占用大量cpu资源,3个线程处于空闲状态
 */
public class HoldCPUMain {
    //大量占用cpu
    public static class HoldCPUTask implements Runnable {
        @Override
        public void run() {
            while (true) {
                double a = Math.random() * Math.random();
                System.out.println(a);
            }
        }
    }

    //空闲线程
    public static class LazyTask implements Runnable {
        @Override
        public void run() {
            try {
                while (true) {

                    Thread.sleep(1000);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        //开启线程,占用cpu
        new Thread(new HoldCPUTask()).start();
        //3个空闲线程
        new Thread(new LazyTask()).start();
        new Thread(new LazyTask()).start();
        new Thread(new LazyTask()).start();
    }
}

总结

  • 使用top命令,拿到CPU占用最高的进程
  • 在使用top -Hp命令,拿到指定进程里面各个线程的CPU占用率;使用top -Hp拿到的线程号是十进制的
  • 使用jstack命令去dump线程,jstack dump出来的线程号是十六进制的,所以在实验的时候,使用printf %x 命令将十进制线程号转成十六进制线程号

JMC

演示

image.png

  • CMD输入jmc打开JMC控制台
  • 点击运行的项目的MBean服务器(1)
  • 打开线程子页面(2)
  • 勾选CPU概要分析(3)
  • 点击刷新(4),找到CPU占用率高的线程(5)
  • 定位有问题的代码(6)

总结

使用JMC去定位CPU占用过高的问题要比第一种方式要简单很多,但是使用JMC是有前提的,JMC需要使用JMX去实现远程连接,如果服务器端的进程没有开启JMX,是没有办法使用JMC的;实际项目中建议开启JMX,从而为定位问题提供方便

可能导致CPU占用率过高的场景与解决方案

  • 无限while循环
    • 尽量避免无限while循环
    • 让循环执行得慢一点(比如说在循环里面弄一个sleep)
  • 频繁得GC(如果垃圾收集非常频繁得话,说明内存分配特别得快,很快相应得内存区就满了,于是导致了下一次得GC;而频繁GC得话就意味着垃圾收集线程将会频繁得执行,这些垃圾收集线程就可能导致CPU得飙升)
    • 尽量降低GC频率
  • 频繁创建新对象(浪费内存,又可能导致CPU过高)
    • 合理使用单例
  • 序列化和反序列化(在做一些解析工作得时候比较常见;序列化和反序列化导致CPU过高,大多数情况都是由于使用类库得方式不合理所导致)
    • 选择合理得API实现功能(可参考地址
    • 选择好用得序列化/反序列化类库
  • 正则表达式(正则表达式使用了一个叫做NFA自动机得引擎,这种引擎在进行字符串匹配得时候,会发生回溯,而一旦发生回溯得话就可能会导致CPU过高得问题;可参考地址
    • 减少字符匹配期间执行得回溯(改写正则表达式,降低回溯得发生)
  • 频繁得线程上下文切换(应用有很多线程在做争抢,线程得状态老是在Blocked和Running之间去切换,而这个切换非常频繁得话就可能会导致CPU占用过高)
    • 降低切换得频率(说起来简单,但事实上需要根据业务进行代码改造,而代码改造得难度取决于业务得复杂度)

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

那一年,我也变成了光!!