jstack 命令解读

jstack 命令是JDK工具之一,使用该命令可以打印正在运行中 Java 进程的栈信息。

Usage:
    jstack [-l] 
        (to connect to running process)
    jstack -F [-m] [-l] 
        (to connect to a hung process)
    jstack [-m] [-l]  
        (to connect to a core file)
Options:
    -F 强制打印线程栈信息,在jstack 命令没有响应的时候添加该选项可以实现强制打印
    -m  (mix)混合模式,可以打印 Java 栈和本地方法栈
    -l  (long listing)长列表模式. 额外打印关于锁的信息

jstack用法

首先使用 jps命令查看需要打印线程栈的java进程号。

D:\Desktop>jps
17984 Launcher
9348 DeathLock

我们这里链接到 9348 这个进程号上,使用下面命令:

D:\Desktop>jstack 9348
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.281-b09 mixed mode):

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x0000018da5881000 nid=0x34c8 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"mythread2" #12 prio=5 os_prio=0 tid=0x0000018dc2547000 nid=0x2580 waiting on condition [0x000000468cdff000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076b8dbf28> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(Unknown Source)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(Unknown Source)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(Unknown Source)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Unknown Source)
        at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(Unknown Source)
        at java.util.concurrent.locks.ReentrantLock.lock(Unknown Source)
        at DeathLock.lambda$deathLock$1(DeathLock.java:24)
        at DeathLock$$Lambda$2/1044036744.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)

可以看到控制台打印出了线程的信息以及线程对应的堆栈信息。下面我们对打印结果进行逐行分析:

java.lang.Thread.State: RUNNABLE 线程状态

首先需要分析的便是线程状态,在 Java 中线程会分别对应着不同的线程状态,从创建线程的 NEW到销毁时的TERMINATED状态。在java.lang.Thread.State类中都有定义,想要分析源码的话可以打开该类进行查看。线程的状态有助于帮我们更好的分析线程的运行过程,下面贴一张图来描述线程之间状态的转换。

private static boolean exit = false;
// 在 new 一个线程对象后,该线程的状态为 NEW 状态
Thread thread1 = new Thread(()->{
	while(!exit){
		// 在执行线程中代码时,线程的状态为 RUNNABLE 状态
		System.out.println("current time is "+new Date())
		// 在执行睡眠代码时,线程会进入 TIMED_WAITTING 状态,并让出 CPU ,等待时间唤醒
		TimeUnit.SECONDS.sleep(1);
	}
});
// 在 start 之后,该线程的状态会进入 RUNNABLE 状态,并且等待系统调度后运行
thread1.start();

jstack 命令解读_第1张图片
通过上述线程状态转移图可以帮助我们分析当前线程所处的状态以及他们运行逻辑。

我们通过 jstack查看线程栈信息时通常看到的最多的是RUNNABLE,BLOCKED,WAITINGTIMED_WAITING这几种状态,我们一般看不到线程的NEWTERMINATED状态,是因为在代码的运行过程中这两种状态只占很小一部分,我们捕捉到这两种状态前这两种状态已经一闪而过了。

"mythread2" #12 分析

以线程名称开头,所有线程都会在第一行打印一些当前线程的运行信息。

"main" #1 prio=5 os_prio=0 tid=0x0000000002e7e800 nid=0x2a1c in Object.wait() [0x0000000002f7f000]
   java.lang.Thread.State: WAITING (on object monitor)

其中开头是线程名称,后面的为线程信息:
#1 表示当前线程ID,从 main线程开始,JVM 根据线程创建的顺序为线程编号。
priopriority优先级的缩写,表名了当前线程的优先级,取值范围为[1-10],默认为 5。在虚拟机进行线程调度的时候会参考该优先级为线程分配计算资源,这个数值越低越有优先获取到计算资源,一般不设置直接使用默认的优先级。
os_prio为线程对应系统的优先级。
nid 本地线程编号NativeID的缩写,对应JVM 虚拟机中线程映射在操作系统中的线程编号。我们可以使用 top 查看进程对应的线程情况进行相关映射。

nid的作用及其使用方式

如上面的描述nid表示的是线程对应的系统本地的线程编号

线程锁解读

当一个线程占有一个锁的时候,线程堆栈会打印一个-locked<0x22bffb60>
jstack 命令解读_第2张图片

// 示例代码
public class Main6 {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        final Thread task1 = new Thread(new Task());
        final Thread task2 = new Thread(new Task());
        task1.start();
        task2.start();
    }
    private static class Task implements Runnable {
        @Override
        public void run() {
            lock.lock();
            int i = 0;
            while (true) {
                i++;
            }
        }
    }
}

当一个线程正在等在其他线程释放该锁,线程堆栈会打印一个-waiting to lock<0x22bffb60>
jstack 命令解读_第3张图片
当一个线程占有一个锁,但又执行在该锁的wait上,线程堆栈中首先打印blocked,然后打印-waiting on <0x22c03c60>
jstack 命令解读_第4张图片

// 示例代码
public class Main7 {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public static void main(String[] args) {
        final Thread task1 = new Thread(() -> {
            synchronized (lock1) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    int i = 0;
                    while (true) {
                        i++;
                    }
                }
            }
        });
        final Thread task2 = new Thread(() -> {
            synchronized (lock2) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    int i = 0;
                    while (true) {
                        i++;
                    }
                }
            }
        });
        task1.start();
        task2.start();
    }
}

使用 jstacktop -Hp获取目标线程的CPU使用情况

通过 jstack 可以打印出当前进程的线程栈信息,但是我们无法获取到这些线栈的 CPU 占用情况,此时,我们可以使用另外一个bash命令 top 去排查高 CPU 占用的线程。
首先使用 jps 命令获取目标 java 进程号:

[ghimi@node01 ~]$ jps
19332 Main7

然后使用 top -Hp 查看该进程下对应线程的CPU 使用情况:

这里如果我们想查看 PID19343且线程名称为Thread-1 的堆栈信息的话(很明显这是我们创建的线程)。
首先我们需要将该线程好打印成 16进制的,可以使用电脑中自带的计算器,或者使用命令都可以:
使用计算器计算线程的16进制:
jstack 命令解读_第5张图片
使用命令行获取16进制:

[ghimi@node01 ~]$ printf "%x\n" 19343
4b8f

接下来使用 jstack 查看线程堆栈信息:

# 
jstack 19332 | grep -10 4b8f


由此我们可以看到目标线程的线程堆栈信息了。

使用 top -Hp 查看目标进程下各个线程的 CPU 使用情况

通过top -Hp 可以查看该进程下各个线程的CPU使用情况。

使用 pstree 查看进程树

通过命令 pstree 可以获取到系统当前运行过程中的进程树,像下面这样:

# -p 选项表示显示详细的进程识别码
pstree -p

jstack 命令解读_第6张图片

使用 ps -Lf 查看进程详细信息

使用 ps -Lf 可以查看目标进程及其线程的信息。

jvm 监控工具Liberica Mission Control

类似于 Java Mission Control,这款工具可以用来监控正在运行当中的 Java 进程这款工具是由贝尔实验室开源的,大概长这样:
jstack 命令解读_第7张图片
简单来看,这款工具能够帮助我们实时的监控虚拟机中进程正在运行的状态。这里不对该工具的使用方式做过多介绍,主要使用该工具去查看一下我们刚刚使用 jstack 命令打印得到的线程信息,由于该工具汉化做的非常好,所以我们可以通过该工具进行对照分析。
jstack 命令解读_第8张图片
下载页面:
jstack 命令解读_第9张图片

参考资料

java线程状态和状态切换
jstack
jvm工具-jstack使用
jstack - 检测死锁、等待、CPU耗时
JVM线程模型详解
jstack日志线程分析
JVM 调优之 jstack 找出最耗cpu的线程
如何使用jstack分析线程状态

你可能感兴趣的:(jstack 命令解读)