JAVA CPU過載問題排查思路以及一鍵查詢腳本

目錄

 

一、常規查詢方法

1、CPU 100%代碼片段

2、問題排查

獲取進程ID

查看進程內的線程ID

將線程ID轉爲16進制

jstack命令查看線程執行情況

二、show-busy-threads 腳本


一、常規查詢方法

當我們遇到JAVA內存泄漏或者CUP居高不下的時候,一般怎麼排查問題呢?

首先我們看段代碼,以下代碼是當用戶輸入任意字符之後,開始啓動三個線程,一個死循環,一個鎖競爭,一個死鎖。啓動之後我們來看下CUP的一個變化。

1、CPU 100%代碼片段

package com.netty;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * 作者:DarkKing
 * 創建日期:2020/2/15
 * 類說明:模擬CPU 佔用 100%
 *
 */

public class TestCpuThread {
    public static void main(String[] args) throws IOException {
        //控制檯輸入控制
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        br.readLine();
        //死循環線程
        createBusyThread();
        br.readLine();
        Object o = new Object();
        createLockThread(o);
        //死鎖
        createDeadLock();

    }

    public static void createBusyThread() {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true)
                    ;
            }
        }, "busyThreadName");
        t.start();
    }

    public static void createLockThread(final Object lock) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(lock) {
                    try {
                        lock.wait();
                    }catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"lockThreadName");
        t.start();
    }
    public static void createDeadLock() {
        Object a = new Object();
        Object b = new Object();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (a) {
                    try {
                        Thread.sleep(3000);
                        synchronized (b) {

                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"t1");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (b) {
                    try {
                        Thread.sleep(3000);
                        synchronized (a) {

                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"t2");
        t1.start();
        t2.start();
    }
}

啓動前

沒有啓動任何JAVA線程,CUP佔用爲0%

啓動後

我們發現CPU被打滿100%。進程爲我們啓動的程序。這個時候我們肯定想知道線程都在幹什麼。導致CUP消耗過高!那麼具體怎麼排查呢?

2、問題排查

獲取進程ID

通過top命令可以看到,最消耗CUP的進程ID。如上圖,得到進程ID爲3030

查看進程內的線程ID

得到進程ID之後,我們可以通過

top -Hp 3030

命令查看進程內的線程ID,如下,找到最耗CUP的線程ID3051。

將線程ID轉爲16進制

printf "%x\n" 3051

jstack命令查看線程執行情況

通過java自帶的jstack命令導出棧信息。發現是busyThreadName線程在執行。查看代碼發現死循環,導致CUP100%。

jstack 3030 | grep beb

二、show-busy-threads 腳本

但是每次查找都要執行那麼多命令實在有點麻煩,步入我們就整合一下,把查找過程放在一個腳本里,豈不是美哉。

代碼如下

#!/bin/bash
# @Function
# Find out the highest cpu consumed threads of java, and print the stack of these threads.
# $ ./show-busy-threads
#ARGS= -p  pid
#[ $? -ne 0] 
PROG=`basename $0`
count=3
redEcho() {
[ -c /dev/stdout ] && {

echo -ne "\003[1;31m"
echo -n "$@"
echo -e "\0ee[0m"
} || echo "$@"

}

	if ! which jstack &> /dev/null; then
    [ -n "$JAVA_HOME" ] && [ -f "$JAVA_HOME/bin/jstack" ] && [ -x "$JAVA_HOME/bin/jstack" ] && {
        export PATH="$JAVA_HOME/bin:$PATH"
    } || {
        redEcho "Error: jstack not found on PATH and JAVA_HOME!"
        exit 1
    }
fi

uuid=`date +%s`_${RANDOM}_$$

cleanupWhenExit() {
    rm /tmp/${uuid}_* &> /dev/null
}
trap "cleanupWhenExit" EXIT

printStackOfThread() {
    while read threadLine ; do
        pid=`echo ${threadLine} | awk '{print $1}'`
        threadId=`echo ${threadLine} | awk '{print $2}'`
        threadId0x=`printf %x ${threadId}`
        user=`echo ${threadLine} | awk '{print $3}'`
        pcpu=`echo ${threadLine} | awk '{print $5}'`

        jstackFile=/tmp/${uuid}_${pid}

        [ ! -f "${jstackFile}" ] && {
            jstack ${pid} > ${jstackFile} || {
                redEcho "Fail to jstack java process ${pid}!"
                rm ${jstackFile}
                continue
            }
        }

        redEcho "The stack of busy(${pcpu}%) thread(${threadId}/0x${threadId0x}) of java process(${pid}) of user(${user}):"
        sed "/nid=0x${threadId0x}/,/^$/p" -n ${jstackFile}
    done
}

[ -z "${pid}" ] && {
    ps -Leo pid,lwp,user,comm,pcpu --no-headers | awk '$4=="java"{print $0}' |
    sort -k5 -r -n | head --lines "${count}" | printStackOfThread
} || {
    ps -Leo pid,lwp,user,comm,pcpu --no-headers | awk -v "pid=${pid}" '$1==pid,$4=="java"{print $0}' |
    sort -k5 -r -n | head --lines "${count}" | printStackOfThread
}

此命令通過結合Linux操作系統的ps命令和jvm自帶的jstack命令,查找Java進程內CPU利用率最高的線程,一般適用於服務器負載較高的場景,並需要快速定位導致負載高的原因。

本腳本來自一個叫候鳥樹的網友,原作者不詳,這裏保留原作者名爲了表示對技術人的尊重,在他的腳本的基礎上做了一些改動。

命令格式:

./show-busy-threads -p 進程號

使用示例:

./show-busy-threads -p 3030

示例輸出:

好啦,以後查找比較耗CPU的線程就比較好找了。另外大家在使用線程工作的時候儘量自己命名線程名稱,方便後期問題排查。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章