一、概述
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输出状态解释