(一)利用jstack定位Java线程堆栈信息

一、概述

  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

(一)利用jstack定位Java线程堆栈信息_第1张图片

这里我们可以看到各个进程对CPU的使用占比,并且可以根据top相关命令进行排序。

2. 查询该进程下占用CPU最高的线程

接下来我们可以根据进程id查询该进程下占用CPU比较高的线程,输入命令:top -Hp PID,其中PID是上面的那个进程id,比如我们这个的:top -Hp 12386

(一)利用jstack定位Java线程堆栈信息_第2张图片

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

(一)利用jstack定位Java线程堆栈信息_第3张图片

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

(一)利用jstack定位Java线程堆栈信息_第4张图片

3. 使用jstack获取对应的线程信息

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

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

(一)利用jstack定位Java线程堆栈信息_第5张图片

这里来简单看下各数据项的含义:

  • 最前面的是线程名称;当我们通过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本身的使用并不复杂,我们来看下它的一些参数:

[email protected]:~$ jstack -help
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)
    jstack [-m] [-l] [server_id@]
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack  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方法);

比如说:

[email protected]:~$ 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输出状态解释

你可能感兴趣的:((一)利用jstack定位Java线程堆栈信息)