BlackChen'site

jstack-线程分析利器全解

jstack-线程分析利器全解

jstack作用

在命令行下, 执行man jstack.

Description : jstack  prints  Java  stack  traces of Java threads for a given Java process or core file or a remote debug server. 

jstack为给定的Java进程或核心文件或远程调试服务器打印Java线程的Java堆栈跟踪

jstack 输出分析

可以使用工具: fastthread,通过可视化输出thread dump内容. 十分方便

"http-nio-8080-Acceptor-0" #41 daemon prio=5 os_prio=31 tid=0x00007fb9d7239000 nid=0x9803 runnable [0x000070000d26b000]
   java.lang.Thread.State: RUNNABLE
	at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
	at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422)
	at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250)
	- locked <0x000000076bb1d4b8> (a java.lang.Object)
	at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:450)
	at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:73)
	at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:95)
	at java.lang.Thread.run(Thread.java:748)
  1. http-nio-8080-Acceptor-0 线程名称
  2. daemon 线程类型:线程分为守护线程 (daemon) 和非守护线程 (non-daemon) 两种,通常都是守护线程;
  3. prio=5 线程优先级:默认为5,数字越大优先级越高;
  4. tid=0x00007fb9d7239000 JVM线程的id:JVM内部线程的唯一标识,通过 java.lang.Thread.getId()获取,通常用自增的方式实现;
  5. nid=0x9803 系统线程id:对应的系统线程id(Native Thread ID),可以通过 top 命令进行查看,现场id是十六进制的形式 top -p pid
  6. runnable 系统线程状态:这里是系统的线程状态,具体的含义见下面 系统线程状态 部分;
  7. 0x00007fbe4ff7c000 起始栈地址:线程堆栈调用的其实内存地址;
  8. java.lang.Thread.State: RUNNABLE JVM线程状态:这里标明了线程在代码级别的状态,详细的内容见下面的 JVM线程运行状态 部分。
  9. 线程调用栈信息:当前线程调用的详细栈信息,用于代码的分析。堆栈信息应该从下向上解读,因为程序调用的顺序是从下向上的。

知识点

JVM线程状态

NEW

至今尚未启动的线程的状态。线程刚被创建,但尚未启动。

RUNNABLE

可运行线程的线程状态。线程正在JVM中执行,有可能在等待操作系统中的其他资源,比如处理器。

BLOCKED

受阻塞并且正在等待监视器的某一线程的线程状态。处于受阻塞状态的某一线程正在等待监视器锁,以便进入一个同步的块/方法,或者在调用 Object.wait 之后再次进入同步的块/方法。

WAITING

某一等待线程的线程状态。线程正在无期限地等待另一个线程来执行某一个特定的操作,线程因为调用下面的方法之一而处于等待状态:

不带超时的 Object.wait 方法,日志中显示为 java.lang.Thread.State: WAITING (on object monitor)
不带超时的 Thread.join 方法
LockSupport.park 方法,日志中显示为 java.lang.Thread.State: WAITING (parking)

TIMED_WAITING

指定了等待时间的某一等待线程的线程状态。线程正在等待另一个线程来执行某一个特定的操作,并设定了指定等待的时间,线程因为调用下面的方法之一而处于定时等待状态:

Thread.sleep 方法
指定超时值的 Object.wait 方法
指定超时值的 Thread.join 方法
LockSupport.parkNanos
LockSupport.parkUntil

TERMINATED

线程处于终止状态。

系统线程状态 (Native Thread Status)

deadlock

死锁线程,一般指多个线程调用期间进入了相互资源占用,导致一直等待无法释放的情况。

runnable

一般指该线程正在执行状态中,该线程占用了资源,正在处理某个操作,如通过SQL语句查询数据库、对某个文件进行写入等。

blocked

线程正处于阻塞状态,指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。

waiting on condition

线程正处于等待资源或等待某个条件的发生,具体的原因需要结合下面堆栈信息进行分析。

  1. 如果堆栈信息明确是应用代码,则证明该线程正在等待资源,一般是大量读取某种资源且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取,或者正在等待其他线程的执行等。

  2. 如果发现有大量的线程都正处于这种状态,并且堆栈信息中得知正等待网络读写,这是因为网络阻塞导致线程无法执行,很有可能是一个网络瓶颈的征兆:

网络非常繁忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读写;
网络可能是空闲的,但由于路由或防火墙等原因,导致包无法正常到达;
所以一定要结合系统的一些性能观察工具进行综合分析,比如netstat统计单位时间的发送包的数量,看是否很明显超过了所在网络带宽的限制;观察CPU的利用率,看系统态的CPU时间是否明显大于用户态的CPU时间。这些都指向由于网络带宽所限导致的网络瓶颈。

  1. 还有一种常见的情况是该线程在 sleep,等待 sleep 的时间到了,将被唤醒。

waiting for monitor entryin Object.wait()

Moniter 是Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者class的锁,每个对象都有,也仅有一个 Monitor。
entry-set-and-wait-set

每个Monitor在某个时刻只能被一个线程拥有,该线程就是 Active Thread,而其他线程都是 Waiting Thread,分别在两个队列 Entry SetWaint Set里面等待。其中在 Entry Set 中等待的线程状态是 waiting for monitor entry,在 Wait Set 中等待的线程状态是 in Object.wait()

  1. "Entry Set"里面的线程.

该Monitor不被其他线程拥有,Entry Set里面也没有其他等待的线程。本线程即成为相应类或者对象的Monitor的Owner,执行临界区里面的代码;此时在Thread Dump中显示线程处于 Runnable 状态。

该Monitor被其他线程拥有,本线程在 Entry Set 队列中等待。此时在Thread Dump中显示线程处于 waiting for monity entry 状态。

临界区的设置是为了保证其内部的代码执行的原子性和完整性,但因为临界区在任何时间只允许线程串行通过,这和我们使用多线程的初衷是相反的。如果在多线程程序中大量使用synchronized,或者不适当的使用它,会造成大量线程在临界区的入口等待,造成系统的性能大幅下降。如果在Thread Dump中发现这个情况,应该审视源码并对其进行改进。

  1. Wait Set 里面的线程
    当线程获得了Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(通常是被synchronized的对象)的wait()方法,放弃Monitor,进入 Wait Set队列。只有当别的线程在该对象上调用了 notify()或者notifyAll()方法,Wait Set队列中的线程才得到机会去竞争,但是只有一个线程获得对象的Monitor,恢复到运行态。Wait Set中的线程在Thread Dump中显示的状态为 in Object.wait()。通常来说,

当CPU很忙的时候关注 Runnable 状态的线程,反之则关注 waiting for monitor entry 状态的线程。

线程类型(daemon, no daemon)

JAVA守护线程 非守护线程
What is a daemon thread in Java?

简单来说: A daemon thread is a thread that does not prevent the JVM from exiting when the program finishes but the thread is still running. An example for a daemon thread is the garbage collection.

案例分析

jstack demo

参考文章
How to Analyze Java Thread Dumps
How to Read a Thread Dump
java命令--jstack 工具
Java 多线程:线程优先级
jstack生成的Thread Dump日志线程状态

评论