目錄
一、常規查詢方法
當我們遇到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的線程就比較好找了。另外大家在使用線程工作的時候儘量自己命名線程名稱,方便後期問題排查。