在分析线上问题时常使用到jstack 命令将当时Java应用程序的线程堆栈dump出来。
面对jstack 日志,我们如何查看?今天一起和大家共同探讨下。
想要通过jstack命令来分析线程的情况的话,首先要知道线程都有哪些状态,下面这些状态是我们使用jstack命令查看线程堆栈信息时可能会看到的线程的几种状态:
-
NEW 未启动的。不会出现在Dump中。
-
RUNNABLE 可运行线程的线程状态。处于可运行状态的某一线程正在Java虚拟机中运行,但它可能正在等待操作系统中的其他资源,比如处理器
-
BLOCKED 受阻塞并且正在等待监视器锁的某一线程的线程状态。处于受阻塞状态的某一线程正在等待监视器锁,以便进入一个同步代码块/方法,或者在调用Object.wait之后再次进入同步代码块/方法。
-
WATING 处于等待状态的线程正等待另一个线程执行特定操作。 例如,已经在某一对象上调用了Object.wait()的线程正等待另一个线程在该对象上调用Object.notify()或Object.notifyAll()。已经调用Thread.join()的线程正在等待指定线程终止。再例如:不带超时值的Object.wait,不带超时值的Thread.join,LockSupport.park等方式
-
TIMED_WATING 具有指定等待时间的某一等待线程的线程状态。某一线程因为调用以下带有指定正等待时间的方法之一而处于定时等待状态,例如:设置固定时间的Thread.sleep,带有超时值的 Object.wait, 带有超时值的 Thread.join, LockSupport.parkNanos, LockSupport.parkUntil。
-
TERMINATED 已退出的。
在多线程的 JAVA程序中,实现线程之间的同步,就要说说 Monitor。 Monitor是Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。下面这个图,描述了线程和 Monitor之间关系,以及线程的状态转换图:
进入区(Entrt Set): 表示线程通过 synchronized 要求获取对象的锁。如果对象未被锁住,则成为 The Owner; 否则则在进入区等待。一旦对象锁被其他线程释放,立即参与竞争。
拥有者(The Owner): 表示某一线程成功竞争到对象锁。
等待区(Wait Set): 表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。
从图中可以看出,一个 Monitor在某个时刻,只能被一个线程拥有,该线程就是**"Active Thread”** ,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在 “Wait Set”中等待的线程状态是 “in Object.wait()”。
堆栈日志格式先说明下日志格式:
"http-nio-5000-exec-111"为线程名称,在平时创建线程或线程池时请务必取一个见明之义的线程名称,方便排查问题;
prio=5:线程优先级,不用关心;
tid=0x000055dcb4453800:线程id,不用关心;
nid=0x51f:操作系统映射的线程id, 非常关键,后面再使用jstack时补充。
waiting for monitor entry:表示线程正在等待获取锁
0x00007f18619b5000:线程栈起始地址
表示线程在方法调用时,额外的重要的操作。线程Dump分析的重要信息。修饰上方的方法调用。
-
locked <地址> 目标:使用synchronized申请对象锁成功,监视器的拥有者。
-
waiting to lock <地址> 目标:使用synchronized申请对象锁未成功,在进入区等待。
-
waiting on <地址> 目标:使用synchronized申请对象锁成功后,释放锁在等待区等待。
-
parking to wait for <地址> 目标
参考《Jstack命令》
总结:
-
wait on monitor entry: 被阻塞的,肯定有问题
-
runnable : 注意IO线程
-
in Object.wait(): 注意非线程池等待
top+jstack查找线上CPU占用最高的线程流程:
1. linux系统中输入如下命令,查看CPU占用最高的进程:
top -c
top -c含义:
每隔5秒显式进程的资源占用情况,并显示进程的命令行参数(默认只有进程名)
2. 查看进程中CPU占比最高的线程
使用 top -H -p [PID] 找出占cpu比例最高的线程的 pid
-H: 设置线程模式
-p: 显示指定PID的进程
linux中通过如下命令,将线程的PID转为小写16进制。
printf '%xn' 3168
得到16进制的线程ID为:c60
3. 导出进程堆栈信息,并定位出问题代码位置
使用 jstack 3147 | grep c60 -A 50
此命令会输出此java进程中的所有的线程栈信息,我们通过有问题的线程的16进制PID查找问题线程对应的栈信息。



