jstack 命令详解
jstack命令用来生成JVM中的线程快照(thread dump),其中包含有每个线程的方法调用栈以及其状态、锁信息等。其用法说明如下所示。
说明一下三个参数的含义:
-F:如果正常执行jstack命令没有响应(比如进程hung住了),可以加上此参数强制执行thread dump。
-m:除了打印Java的方法调用栈之外,还会输出native方法的栈帧。
-l:打印与锁有关的附加信息。使用此参数会导致JVM停止时间变长,在生产环境需慎用。
jstack是在线程级别定位JVM问题的利器,但前提是得读懂thread dump,我们举例说明。
线程快照
在每个线程的快照的第一行,包含有线程名、是否为守护线程、优先级、线程ID等信息,第二行则是线程状态,下面就是方法调用栈了。下图是Java线程状态转换的示意,老生常谈。
jstack线程快照中的状态与图示相同,只是没有NEW状态而已。我们逐一进行分析,在分析之前,先放出Java管程对象ObjectMonitor的简图。看官也可以通过我之前写的这篇文章来了解管程。
RUNNABLE
线程正在运行。如果在其调用栈中看到locked <地址>的提示,表示该方法正持有锁,即该线程位于Owner区内。
BLOCKED
线程处于阻塞状态,即正在等待锁被其他线程释放。在其调用栈的栈顶方法会看到waiting to lock <地址>的提示,表示该方法试图持有锁,线程正在Entry Set区等待。
WAITING
线程处于无限等待的状态。又分为两种情况:
- on object monitor:线程已经获得锁,调用了不带超时参数的Object.wait()/Thread.join()方法,线程进入管程的Wait Set区。在其调用栈中会看到locked <地址>的提示。
- parking:调用了LockSupport.park()方法,线程直接进入挂起状态(park是Unsafe提供的低级原语)。在其调用栈的栈顶方法会看到parking to wait for <地址>的提示。
TIMED_WAITING
线程处于有限等待的状态。它分为三种情况,除了与WAITING相同的on object monitor(获得锁并调用带超时的Object.wait()/Thread.join()方法)和parking(调用带超时的LockSupport.parkNanos()/parkUntil()方法)之外,还有一种sleep,即通过Thread.sleep()使线程睡眠。
通过分析线程快照的状态和调用栈,可以让我们快速地定位造成Java程序表现异常的症结,如死锁、热锁(很多线程竞争同一块临界区造成大量BLOCKED)、高CPU占用、I/O长时间阻塞(注意此时线程状态可能是RUNNABLE)等。下面举两个具体的例子。