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