生成thread dump
1 | jstack <PID> |
查看thread dump文件
- 保存成
xxx.tdump文件,用jvisualvm打开; - ✔ 用网站打开: http://spotify.github.io/threaddump-analyzer/
- 用文本编辑器打开.
其中第二个方法,用网站查看最好用。
此外还可以用另一个网站: http://fastthread.io/
这个网站还能看core dump和hs_err_pid142043.log
jvisualvm查看thread dump
和用文本编辑器的区别只有一个,就是多了语法高亮(字体颜色有区分)。
某个日志如下:
1 | "Attach Listener" #16866 daemon prio=9 os_prio=0 tid=0x00007f6574001000 nid=0x2edea waiting on condition [0x0000000000000000] |
各部分含义:
- 线程名称:
Attach Listener - 线程类型:
daemon - tid: jvm内部线程id;
- nid: 线程id(系统真实)16进制;
- 线程状态: RUNNABLE
- [0x0000000000000000]: 起始栈地址。
案例: 遇到cpu100%的情况
- 查看某进程下占用高的线程id:
top –H –p <进程id>
这里从结果里得到比如 16143 线程id; - 查看thread dump:
jstack -l <进程id>
上一步的线程id(16143)转换为16进制为0x3f0f,因此在thread dump中看看0x3f0f相关信息即可。(也就是上文中的nid)
(可以定位到具体是啥线程了)
网站查看thread dump
http://spotify.github.io/threaddump-analyzer
几个改进:
- 会合并相同栈的线程; (类似一个group by);
- 会列出锁占用情况(
Synchronizers面板); - Top Methods From 50 Running Threads。
tip 10进制转16进制
用top -H -p pid找到cpu占用高的线程id(10进制)以后,要去thread dump里找具体是哪个nid(16进制),因此需要用这个linux命令:
1 | # 把10进制的26转成16进制: |
或者用printf:
1 | printf %x'\n' 26 |
线程状态
- java中的线程状态:
线程状态转化大致如下:
New=>Runnable=>Terminated// 理想状态Runnable=>Blocked/Waiting/Time_Waited=>Runnable// 可能误入的歧途
| 状态 | 说明 |
|---|---|
| New | 创建线程后,调用start()前. |
| Runnable | 就绪和运行中. |
| Terminated | 终止. 执行完毕 |
| Blocked | 阻塞. 阻塞于锁. (synchronized) |
| Waiting | 等待. 等待其他线程的中断或者通知.(Lock类) |
| Time_Waiting | 超时等待. 比Waiting多一个超时返回功能.(Lock类) |
thread dump中的线程状态:
示例状态与相应线程名:1
2
3
4
5
6// java.lang.Thread.State: RUNNABLE
RUNNABLE: Attach Listener
TIMED_WAITING (parking): HikariPool-1 housekeeper
TIMED_WAITING (on object monitor): Abandoned connection cleanup thread
TIMED_WAITING (sleeping): Hashed wheel timer
WAITING (on object monitor): Finalizer
状态对应含义详解:
1 | Runnable: 就绪或运行中 |
其中synchronized与obj.wait()这对好基友:
对象锁: 每个对象的临界区Monitor
线程栈示例
1 | "Abandoned connection cleanup thread" #81 daemon prio=5 os_prio=0 tid=0x00007f66acbbc000 nid=0x279b in Object.wait() [0x00007f6646a8b000] |
先lock0x0000000080210720,然后wait.
可以想象代码可能是形如:
1 | synchronized(obj) { |
Monitor、Entry Set、Waiting Set
Monitor的两个set: Entry Set, Waiting Set.
首先得进入Entry Set,才能通往Waiting Set,因此要先Synchronized才行。Entry Set: synchronizedWaiting Set: obj.wait()// syn后。// 会释放对象锁。
唤醒Waiting Set: 在synchronized后调用notify
问题定位
Cpu很忙:检查runnable的线程;
- 隔一段时间,收集几次thread dump;
- 比较其中runnable的代码变化。
没变化=>可能在死循环;
有变化=>正常。
Cpu很闲:检查waiting for monitor entry的线程。
- 隔一段时间,收集几次thread dump;
- 比较其中waiting for monitor entry的代码变化。
没变化=>可能在死等(死锁); =>可以
有变化=>正常。
Thin Lock, Fat Lock, Spin Lock:
Thin Lock: 某线程通过CAS获得thin lock,其他线程进入spin lock;fat lock: monitor_enter/monitor_exit,其他线程进入wait(),不消耗cpu;
// 第二个获得锁的人把锁升级成fat lock。// 不能回退/降级
Tasuki锁:(Oracle的BEA JRockit与IBM的JVM)
thin lock取消忙等待;fat lock可以回退/降级,通过采样数据分析决定。