读懂jstack

1 jstack

jstack是JDK自带的一种线程栈跟踪工具,用于生成java虚拟机当前时刻线程快照。在定位线程卡顿、死锁、block等原因的时候非常有用。使用方法是:

jstack [-l] pid

2 Monitor

Monitor是java中用以实现线程同步和互斥的主要手段,每一个对象有且只有一个monitor,可以理解monitor就是一个对象锁。用下面这张著名的图来描述线程和monitor的关系
monitor

  • Entry Set中的线程正在通过synchronized要求获取到对象锁。如果获取到锁就会进度The Owner,没有获取到锁就会在Entry Set等待
  • The Owner中的线程成功竞争到锁
  • Wait Set表示线程调用wait方法,释放对象锁,并在Wait Set等待。当调用notify或者notifyAll后,处在Wait Set中的一个(notify)或者多个(notifyAll)线程就会被放到Entry Set中同其他没有获取到锁的线程一起竞争锁。

其实调用wait方法可以分为三个操作:

  1. 释放锁并阻塞(放到monitor的Wati Set中)
  2. 等待条件发生(时间到或者调用了notify或者notifyAll)
  3. 获取通知后,竞争获取锁(放到Entry Set中)

那么对于Jstack打印的线程栈来讲,处在EntrySet中的线程动作是"Waiting for monitor entry", 处在The Owner中的线程动作是“runnable”,处在Wait Set中的线程动作是“in Object.wait()”。

3 线程栈内容

网上有很多介绍jstack中线程状态的,但是讲解的比较乱,这里做下总结。
不同的jvm可能打印出的线程栈是不同的,当前使用的java版本如下

java version "1.8.0_111"
Java(TM) SE Runtime Environment (build 1.8.0_111-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode)

如下jstack打印出的线程栈快照
jstack

3.1 线程动作

线程动作在jstack中第一行,表示线程在做什么,个人理解起到概括的作用(网上很多把这块也当做线程状态,我觉得不太合适),之后才是真正的栈

  • runnable 正在运行
  • in Object.wait() 如上面介绍的,调用了wait方法,在Wait Set中
  • waiting for monitor entry, 在Entry Set,等待获取锁
  • waiting on condition 在等待某个状态的发生,如sleep,时间到后就会活跃,或者io阻塞

3.2 线程状态

第二行才是真正的线程状态,线程状态是在java.lang.Thread.State中已经定义了的(java.lang.Thread.State是个枚举类),我们可以看下java源码

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

可以看到java的线程状态一共是6种,分别是:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

  • BLOCKED 线程在等待获取锁,可以理解为在Entry Set中
  • WAITING 由于调用了以下方法线程会变为WAITING状态:wait join park
  • TIMED_WAITING 由于调用了以下放啊发线程会变为TIMED_WAITING:sleep wait(long) join(long)

3.3 调用修饰

  • locked <地址> 获取到对象锁,monitor的y拥有者
  • wating to lock <地址> 在Entry Set等待获取锁
  • waiting on <地址> 获取到锁对象后,释放锁在Wait Set等待
  • parking to wait for <地址>

3.4 其他

  • prio 线程优先级
  • tid 表示线程id
  • nid 操作系统映射的线程id
  • [0x0000700004843000] 表示线程栈的其实地址或者锁地址

4. 举例

使用jstack查看线程栈的时候应该从下往上看,如下:
jstack举例
为什么会即锁住一个对象,又在一个对象的Wait Set 等待呢? 其实我们从下往上按照时间线来看就说的通了,线程首先锁住了该对象,即locked <0x000000076abe6c00>(过去式),然后调用了wait,被放到了Wait Set中,即waiting on <0x000000076abe6c00>

java类似如下:

class Producer implements Runnable {
        private Queue<Integer> buffer;

        public Producer(Queue<Integer> buffer) {
            this.buffer = buffer;
        }
        @Override
        public void run(){
            while (true) {
                synchronized (buffer) {
                    Thread.currentThread().setName("producer");
                    while (buffer.size() >= 10) {
                        try {
                            buffer.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Integer time = new Random().nextInt(100);
                    try {
                        Thread.sleep(time);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("producer remove: " + time);
                    buffer.add(time);
                    buffer.notifyAll();
                }
            }
        }
    }

5. 参考

http://www.cnblogs.com/kongzhongqijing/articles/3630264.html

原文地址:https://www.cnblogs.com/set-cookie/p/8727374.html