生成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
: synchronized
Waiting 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
可以回退/降级,通过采样数据分析决定。