一、常规查询方法
当我们遇到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的线程就比较好找了。另外大家在使用线程工作的时候尽量自己命名线程名称,方便后期问题排查。