美文网首页linux工具我用 LinuxJava学习笔记
线上linux系统故障排查之一:CPU使用率过高

线上linux系统故障排查之一:CPU使用率过高

作者: 爱情小傻蛋 | 来源:发表于2017-07-12 10:43 被阅读659次

    《线上linux系统故障排查之一:CPU使用率过高》
    《线上linux系统故障排查之二:内存占用过高》

    一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环。
    下面我们将一步步定位问题,详尽的介绍每一步骤的相关知识。

    一、通过top命令定位占用cpu高的进程

    执行top命令得到以下结果:


    top命令执行结果

    通过上图可以明显看出进程PID41843占用cpu过高,明显存在问题,定位到了进程id。当然如果你想只观察进程PID41843的CPU和内存以及负载情况,可以使用以下命令

    top -p 41843。

    结果如下:


    top -p 41843命令执行结果

    这里顺便解释下上图各个参数的意义,有利于读者更好的排查问题。

    1. 第一行是任务队列信息
      top - 14:06:34 up 537 days, 6 min, 6 users, load average: 0.41, 0.45, 0.43
    任务队列信息 含义
    14:06:34 当前时间
    537 days 系统运行时间
    6 min 用户在线时间
    6 users 在线用户数
    load average: 0.41, 0.45, 0.43 系统负载,即任务队列的平均长度。1分钟前、5分钟前、15分钟前平均负载

    2)第二行为进程的信息

    进程信息 含义
    Tasks: 1 total 进程总数
    0 running 正在运行的进程数
    1 sleeping 睡眠的进程数
    0 stopped 停止的进程数
    0 zombie 僵尸进程数
    1. 第三行为cpu信息
    cpu信息 含义
    6.1% us 用户空间占用CPU百分比
    1.5% sy 内核空间占用CPU百分比
    0.0% ni 用户进程空间内改变过优先级的进程占用CPU百分比
    92.2% id 空闲CPU百分比
    0.0% wa 等待输入输出的CPU时间百分比
    0.0% hi 硬件中断
    0.0% si 软件中断
    0.0%st 实时
    1. 第四、五行为内存信息。
      内容如下:
    物理内存信息 含义
    Mem: 191272k total 物理内存总量
    173656k used 使用的物理内存总量
    17616k free 空闲内存总量
    22052k buffers 用作内核缓存的内存量
    交换区信息 含义
    Swap: 192772k total 交换区总量
    0k used 使用的交换区总量
    192772k free 空闲交换区总量
    123988k cached 缓冲的交换区总量

    二、通过top命令定位问题进程中每个线程占用cpu情况

    通过问题进程中每个线程占用cpu情况使用可以使用如下命令:

    top -p 41843 -H

    查看进程PID41843的每一个线程占用CPU情况,如图。


    top -p 41843 -H的执行结果

    由上图明显可以发现,线程PID41892CPU占用率最高,接下来定位该线程的代码是否出现异常导致cpu占用过高。

    三、通过jstack 命令定位问题代码

    上一步发现PID41892占用的CPU过高,就将这个PID转换成16进制,易知,PID41892转化成16进制为a3a4。使用如下命令命令定位问题代码:

    jstack 41892 | grep a3a4

    输出如下:

    "Thread" prio=10 tid=0x00007f950043e000 nid=0x54ee in test();

    可以分析得到: 线程Thread下的wait()函数cpu使用率很高,查看源代码中的test()函数代码如下:

    public void test(){
      while(true){
         for(int i = 0 ;i<100;i++);
      }
    }
    

    while循环无法结束,一直抢占cpu,导致程序cpu使用过高,修改代码即可。
    到此为止,因为代码问题导致的cpu使用过高的故障排查方法就介绍完了。

    tips:由于内存的原因,频繁的GC,也可能导致cpu使用过高,内存的解决方案在文章《线上linux系统故障排查之二:内存占用过高》中介绍。

    四、终极大杀器

    笔者是喜欢一、二、三步骤定位cpu使用率过高的问题,但是如果读者觉得以上方法不喜欢或者以上无法定位问题时,可以使用jstack方法获取进程PID41843包含的运行线程的所有状态。使用如下命令:

    jstack -l 41843
    或者将结果输入在dump.txt文档中:jstack -l 41843 >> dump.txt

    首先我们来介绍下,Jstack的输出中,Java线程状态的四种状态的概念:

    RUNNABLE 线程运行中或I/O等待
    BLOCKED 线程在等待monitor锁(synchronized关键字)
    TIMED_WAITING 线程在等待唤醒,但设置了时限
    WAITING 线程在无限等待唤醒

    接下来用简单的代码模拟这4种状态的发生,同时介绍下问题的排查方法。
    1) 让线程一直处于RUNNABLE状态

    public static void main(String[] args) {
        printPId();//打印进程id
    
        runnable();//模拟RUNNABLE状态方法,第14行
    }
    
    public static void runnable(){//第19行
        int i = 0;
        while (true) {
            i++;
        }
    }
    
    public static void printPId() {
        String name = ManagementFactory.getRuntimeMXBean().getName();
        System.out.println(name.split("@")[0]);
    }
    

    执行main函数,使用命令jstack -l PID(pid为printPId函数打印的值),得到以下输出信息:状态为RUNNABLE,程序在Test类的runnable()方法中占用cpu,一般该状态不会出现问题。但是如果多次执行该命令,发现cpu在持续执行该方法,且cpu使用过高,可以排查runnable()方法是否存在死循环等持续占用cpu的情况。

    "main" #1 prio=5 os_prio=0 tid=0x00000000023fe000 nid=0x2ab8 runnable [0x0000000
    0026df000]
    java.lang.Thread.State: RUNNABLE
    at Test.runnable(Test.java:19)
    at Test.main(Test.java:14)

    Locked ownable synchronizers:
    -None

    2) 让线程一直处于BLOCKED状态(重点关注)

    public static void main(String[] args) {
        printPId();//打印进程id
    
        blocked();//模拟BLOCKED状态方法,第16行
    }
    
    public static void blocked(){
        Integer lock = 0;
        new Thread() {
            public void run() {
                synchronized (lock) {
                    try {
                        Thread.sleep(1000 * 1000);//26行
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    
        try { Thread.sleep(100); } catch (Exception e) {e.printStackTrace();}
    
        synchronized (lock) {
            try {
                Thread.sleep(10 * 1000);//第37行
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void printPId() {
        String name = ManagementFactory.getRuntimeMXBean().getName();
        System.out.println(name.split("@")[0]);
    }
    

    主线程sleep,先让另外一个线程拿到lock,并长期持有lock(sleep会持有锁,wait不会)。此时主线程会BLOCK住等待lock被释放,此时jstack的输出可以看到main线程状态是BLOCKED。
    根据以下输出可以看出:
    1)线程状态是 Blocked,阻塞状态。说明线程等待资源超时!
    2)“ waiting to lock <0x00000000d6f7a970>”,指该地址被占用了,线程在等待给这个 0x00000000d6f7a970地址上锁。
    3)在 dump 日志里查找字符串 0x00000000d6f7a970,如果发现有大量线程都在等待给这个地址上锁。则存在问题。继续查找字符串0x00000000d6f7a970,发现locked <0x00000000d6f7a970> (a java.lang.Integer)给地址上锁,却未释放,查找该出代码问题。
    4)“waiting for monitor entry”说明此线程通过 synchronized(obj) {……} 申请进入了临界区。
    5)第一行里,"main"是 Thread Name 。tid指Java Thread id。nid指native线程的id。prio是线程优先级。[0x00000000027af000]是线程栈起始地址。

    "main" #1 prio=5 os_prio=0 tid=0x000000000244e000 nid=0x2218 waiting for monitor
    entry [0x00000000027af000]
    java.lang.Thread.State: BLOCKED (on object monitor)
    at Test.blocked(Test.java:37)
    -waiting to lock <0x00000000d6f7a970> (a java.lang.Integer)
    at Test.main(Test.java:16)

    Locked ownable synchronizers:
    -None

    "Thread-0" #10 prio=5 os_prio=0 tid=0x0000000058893800 nid=0x1cc8 waiting on con
    dition [0x00000000599ae000]
    java.lang.Thread.State: TIMED_WAITING (sleeping)
    at java.lang.Thread.sleep(Native Method)
    at Test$1.run(Test.java:26)
    -locked <0x00000000d6f7a970> (a java.lang.Integer)

    Locked ownable synchronizers:
    -None

    3) 让线程处于TIMED_WAITING状态

    public static void main(String[] args) {
       printPId();//打印进程id
    
       timedWaiting();//模拟TIMED_WAITING状态方法
    }
    
    public static void timedWaiting() {
        Integer lock = 0;
        synchronized (lock) {
           try {
               lock.wait(100 * 1000);
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
    }
    
    public static void printPId() {
       String name = ManagementFactory.getRuntimeMXBean().getName();
       System.out.println(name.split("@")[0]);
    }
    

    根据以下输出可以看出:
    1)“TIMED_WAITING ”中的 timed_waiting 指等待状态,但这里指定了时间,到达指定的时间后自动退出等待状态。

    "main" #1 prio=5 os_prio=0 tid=0x0000000001c1e000 nid=0x1c78 in Object.wait() [0
    x000000000272f000]
    java.lang.Thread.State: TIMED_WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    -waiting on <0x00000000d6f7a970> (a java.lang.Integer)
    at Test.timedWaiting(Test.java:25)
    -locked <0x00000000d6f7a970> (a java.lang.Integer)
    at Test.main(Test.java:18)

    Locked ownable synchronizers:
    -None

    4) 让线程处于WAITING状态状态

    public static void main(String[] args) {
      printPId();//打印进程id
    
      waiting();//模拟waiting状态方法
    }
    
    public static void waiting() {
      Integer lock = 0;
      synchronized (lock) {
          try {
              lock.wait();
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
    }
    
    public static void printPId() {
      String name = ManagementFactory.getRuntimeMXBean().getName();
      System.out.println(name.split("@")[0]);
    }
    

    无超时的等待,必须等待lock.notify()或lock.notifyAll()或接收到interrupt信号才能退出等待状态。同理,ReentrantLock.lock()的无参方法调用,也会使线程状态变成WAITING。

    "main" #1 prio=5 os_prio=0 tid=0x00000000024ce000 nid=0x2b14 in Object.wait() [0
    x00000000025ef000]
    java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    -waiting on <0x00000000d6f7a970> (a java.lang.Integer)
    at java.lang.Object.wait(Unknown Source)
    at Test.waiting(Test.java:27)
    -locked <0x00000000d6f7a970> (a java.lang.Integer)
    at Test.main(Test.java:20)

    Locked ownable synchronizers:
    -None

    掌握各个线程的状态和信息,将无往而不利~~

    相关文章

      网友评论

        本文标题:线上linux系统故障排查之一:CPU使用率过高

        本文链接:https://www.haomeiwen.com/subject/awzchxtx.html