1. 关于jstack的说明

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

jstack用于打印一个JAVA进程里面的线程堆栈,是我们排查性能瓶颈时常用的工具。

2. 准备知识

2.1 Java Thread的状态

一般操作系统中线程的生命周期是这样的:

对应到JAVA中的线程状态则变成了如下图所示。控制流箭头标注了触发状态改变的action。

可以看到这里我没有写RUNNING的状态。其实JAVA中Thread类的定义中也确实没有RUNNING的状态。这不代表线程没有RUNNING状态,只不过在JAVA语言中,没必要特地定义RUNNING状态,只要处于RUNNABLE,后面运行的事情就是OS来管理了。OS会负责将RUNNABLE的JAVA线程进行调度,过程中也可能经历Suspend等状态。

2.2 jstack的输出格式


上图是本地一个JAVA进程的jstack命令使用后的部分输出,部分主要元素的解释如下:

  • prio:JAVA线程的优先级,取值为1-10,默认为5,数值越大优先级越高。这个和LINUX的优先级(PR,NI)正好相反。
  • tid:线程id
  • nid:操作系统映射的线程id, 16进制,转化成10进制后可以在top命令中定位耗CPU比较高的JAVA线程。不过现在有开源工具greys了,建议用工具直接看吧哈哈。
  • 0x000070000b959000 :表示线程栈的起始内存地址

2.3 小示例

代码:

package concurrent.lock;

import java.util.concurrent.TimeUnit;

/**
 * Created by wanshao
 * Date: 2017/11/27
 * Time: 上午11:00
 **/
public class JstackExample {
    public static void main(String[] args) {
        //WAITING example,这个例子现在没法使得其他线程BLOCKED了,可见synchronized的实现已经有所不同,不会一上来就用最重的锁。
        myExample();

    }

    private static void myExample() {
        final Thread thread = new Thread() {
            public void run() {
                synchronized (this) {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        //wait();       //
                        sleep(30000);   //使用sleep的时候,main方法被阻塞,等待kami线程占用的锁
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
        thread.setName("kami");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        synchronized (thread) {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(30);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            thread.notify();
        }
    }
    
}

kami线程使用sleep

可以看到main在进行等待获取到锁,此时进入waiting for monitor entry,并是阻塞状态。而kami线程提前获取到锁,当由于调用了sleep此时进入到Timed_waiting状态,此时kami线程锁住的对象地址是76aca5fc8,而main正在等待获取这个锁对象。

kami线程使用Object.wait()

总结: 可与看到kami线程sleep执行完毕后就在等待main线程释放锁了。释放锁之后需要notify来唤醒才能再从WAITING变成RUNNABLE