一、概述
JDK本身提供了许多方便的性能调优及监控的小工具,这些小工具虽然没有说是官方的标准工具,但自从Java诞生这么多年,这些工具一直在被人使用,不得不说这是JDK给开发者的福利,这些工具包含但不仅限于:jstack、jps、jmap、jConsole、jstat等等。
由于前些时候用到了jstack这个工具,所以今天来简单总结下jstack的使用。
二、jstack使用
首先,jstack会生成JVM当前时刻的线程快照,然后我们可以通过它查看某个Java进程内的线程堆栈信息,通常来说,当线上CPU使用率较高的时候,我们可以通过jstack查询占用CPU较高的一些线程的使用情况,比如发生了死锁,线程阻塞等相关操作。一般情况下,jstack会配合其他命令一块进行操作,比如top,ps等命令。我们先来看下使用jstack命令的一些步骤。
1. 查询占用CPU最高的进程
首先,我们通过top命令查询当前CPU的使用情况,top命令前面已详细说过,这里不多说,直接输入命令:top

这里我们可以看到各个进程对CPU的使用占比,并且可以根据top相关命令进行排序。
2. 查询该进程下占用CPU最高的线程
接下来我们可以根据进程id查询该进程下占用CPU比较高的线程,输入命令:top -Hp PID,其中PID是上面的那个进程id,比如我们这个的:top -Hp 12386:

如果对PS命令比较熟的,还可以直接通过:ps H -eo pid,tid,pcpu | sort -n -k 3 | tail -10 来定位到相关线程:

如果想看下线程的详细信息,还可以通过:cat /proc/进程号/task/线程号/status来查看。如果需要查询该进程下所有的线程,我们还可以通过pstree命令,以树的形式展示:

3. 使用jstack获取对应的线程信息
这里我们获取到了对应的线程id,由于jstack输出中的线程id是16进制的,所以需要先将线程id转为16进制:

然后使用jstack命令查看对应的堆栈信息:jstack $pid|grep -A N $nid,其中此处的pid指的是进程号,而nid指的是该进程下占用最多的线程号,比如:jstack 12386|grep -A 30 30c6:

这里来简单看下各数据项的含义:
- 最前面的是线程名称;当我们通过Thread来创建线程的时候,线程会被命名为
Thread-(序号);当我们使用连接池通过ThreadFactory来创建线程时,线程会被命名为pool-(序号)-thread-(序号); - tid,指的是线程id;
- prio,指的是线程优先级,值越大优先级越高;
- os_prio,表示的对应操作系统线程的优先级,由于并不是所有的操作系统都支持线程优先级,所以可能会出现都为0的情况;
- nid,操作系统映射的线程id,每一个java线程都有一个对应的操作系统线程;
- java.lang.Thread.State:RUNNABLE,当前线程的状态;如果是WATTING状态,后面会跟上调用哪个方法导致的watting状态;
- 另外线程是否持有锁信息等,如果持有锁,则是locked<>;而如果是正在等待获取锁,则是 wating for <>;
grep命令中的 -A 表示显示后面若干行,前面也已经学习过这个命令,不多说了。本例中可以看到的是Kafka一直在消费数据,正常来说,除了确实是那种需要密集计算的应用之外,一个应用的CPU都不会太高,除非出现了一些其他的异常情况。
另外,我们还可以将该进程的相关线程信息导出到stack.dump文件中,然后我们可以在这个文件中查看每个线程的具体状态:jstack pid(进程pid)>stack.dump,这里就不多说了。
三、jstack语法
jstack本身的使用并不复杂,我们来看下它的一些参数:
deploy@127.0.0.1:~$ jstack -help
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
(to connect to a hung process)
jstack [-m] [-l] <executable> <core>
(to connect to a core file)
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
(to connect to a remote debug server)
Options:
-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
-m to print both java and native frames (mixed mode)
-l long listing. Prints additional information about locks
-h or -help to print this help message
其中参数文档已经说的很明确了,这里再说下:
- -l 会打印出额外的锁信息,在发生死锁时可以用jstack -l pid来观察锁的持有情况;
- -m 不仅会输出Java堆栈信息,还会输出C/C++堆栈信息(比如Native方法);
比如说:
deploy@127.0.0.1:~$ jstack -l 27075 | grep -A 50 6a15
"pool-1-kafka@thread-1" #45 prio=5 os_prio=0 tid=0x00007f63b1f18000 nid=0x6a15 runnable [0x00007f6324c6b000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
...
- locked <0x000000072028f2a8> (a sun.nio.ch.Util$2)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
...
at org.apache.kafka.clients.consumer.KafkaConsumer.pollOnce(KafkaConsumer.java:1096)
at org.apache.kafka.clients.consumer.KafkaConsumer.poll(KafkaConsumer.java:1043)
at ****.KafkaConsumerWorker.run(KafkaConsumerWorker.java:39)
...
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- <0x000000072012cbb0> (a java.util.concurrent.ThreadPoolExecutor$Worker)
"Curator-PathChildrenCache-0" #44 daemon prio=5 os_prio=0 tid=0x00007f63b1ce4800 nid=0x6a14 waiting on condition [0x00007f6324538000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007201df280> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
这里可以看到当前线程类型:daemon ,当前线程正在等待获取锁 0x00000007201df280,并且当前线程没有持有锁;不过我们更多的是关注用户线程。
这里,涉及到了Locked ownable synchronizers,这里其实有点没看懂,如果表示是该线程锁定的资源的话,那上面locked就会展示了;在stackoverflow上看了下,"ownable synchronizers" list只会出现write lock,而不会出现read lock;然后看了下官方文档:
An ownable synchronizer is a synchronizer that may be exclusively owned by a thread and uses AbstractOwnableSynchronizer (or its subclass) to implement its synchronization property. ReentrantLock and ReentrantReadWriteLock are two examples of ownable synchronizers provided by the platform.
不过还是没有看太懂,以后看到的时候再补充吧(TODO)。
有关Locked ownable synchronizers的说明,可以参考:what-is-locked-ownable-synchronizers-in-thread-dump-stackoverflow.com
四、jstack注意事项
在使用jstack之前,我们肯定是需要了解线程的几种状态的,而在这里我们需要关注的一般有如下几种:
- RUNNABLE,线程处于执行中;一般指该线程正在执行状态中,该线程占用了资源,正在处理某个请求,或者正在进行资源的操作等;
- BLOCKED,线程阻塞;
- WAITING,线程正在等待中;Waiting on condition(等待资源或条件),Waiting for monitor entry(waiting to lock ,等待获取锁);
- Deadlock ,死锁;一个线程锁了某个资源A,等待另一个资源B;而另一个线程恰好锁了这个被等待的资源B,在等待资源A;
另外,还需要注意:
- 如果我们要通过dump来查看问题的话,那一次dump可能不足以确认问题,可以产生多次 dump信息,如果每次 dump都指向同一个问题,我们基本就可以确定问题的所在。
- 使用jstack的时候,需要对应的服务器权限,需要注意,如果没有权限的话,会提示你不允许的操作。
而有关jstack更多操作,可以参考以下链接:
JVM性能分析工具jstack介绍
JVM故障分析系列之四:jstack生成的Thread Dump日志线程状态
jstack线程dump输出状态解释
