在性能调优的时候,apm监控可以看到一直卡在mysql的DruidDataSource.getConnection()
这个时候数据库的资源使用情况cpu、内存、网络均正常,dubbo容器的cpu等资源也正常
抓取线程快照查看线程为何一直处于数据库等getConnection
获取线程快照
在服务端连续三次将线程快照down下来,命令如下
jstack pid > 文件名.txt
- pid为需要抓取的进程数
- 抓线程快照,必须是在加压,且压到出现问题时抓取,连续抓取3~5次
有两种方式可以分析线程快照
- 直接使用文本的方式
- 使用jdc工具分析 下载链接
下面针对两种方式说明具体的分析方式
文本查看方式
一、查看全局锁
需要关注的就是看有没有全局锁锁住其他线程
直接用文本编辑器打开,搜索关键字“- waiting to lock”,统计一下数量
如果很多线程在这个状态,说明有全局锁堵住线程了,比如以上线程有11个在等待锁
二、查看持有几把锁
看有几把锁,就是看- waiting to lock后面的锁地址有几个不同的
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
可以看到这11个线程只有1把锁
三、查看锁是哪个线程持有
直接复制锁地址在线程快照文件中全局搜索,找到 - locked + 锁地址(比如locked <0x00000007004d9ee0>
),即可找到锁的持有线程
四、查看持有锁线程具体执行的事务
找到线程锁持有的线程,查看这个线程的堆栈看到它在干嘛,从堆栈上找程序实现代码,这样就可以知道这把锁是什么场景加的,上面的堆栈中可以看到线程在查db后加密
jac工具查看
一、查看统计视图
二、waiting on monitor的线程
如果当前有全局锁,这里一定很多在是等待。如果是在等锁,对应的状态是BLOCKED
- 线程状态
waiting for monitor entry
:
意味着在等待进入一个临界区 ,所以它在"Entry Set"队列中等待
此时线程状态一般都是 Blocked:java.lang.Thread.State: BLOCKED (on object monitor)
如果大量线程在这个状态,可能是一个全局锁阻塞住了大量线程
如果短时间内打印的 thread dump 文件反映,随着时间流逝,waiting for monitor entry 的线程越来越多,没有减少的趋势,可能意味着某些线程在临界区里呆的时间太长了,以至于越来越多新线程迟迟无法进入临界区
- 线程状态
waiting on condition
:
说明它在等待另一个条件的发生,来把自己唤醒,或者干脆它是调用了 sleep(N)
此时线程状态大致为以下几种:
java.lang.Thread.State: WAITING (parking):一直等那个条件发生
java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定时的,那个条件不到来,也将定时唤醒自己
如果大量线程在在这个状态,可能是它们又跑去获取第三方资源,尤其是第三方网络资源,迟迟获取不到Response,导致大量线程进入等待状态
所以如果发现有大量的线程都处在 Wait on condition,从线程堆栈看,正等待网络读写,这可能是一个网络瓶颈的征兆,因为网络阻塞导致线程无法执行
- 线程状态
in Object.wait()
:
说明它获得了监视器之后,又调用了 java.lang.Object.wait() 方法
每个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在 “Wait Set”中等待的线程状态是 “in Object.wait()”
当线程获得了 Monitor,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被 synchronized的对象)的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列
此时线程状态大致为以下几种:
java.lang.Thread.State: TIMED_WAITING (on object monitor);
java.lang.Thread.State: WAITING (on object monitor);
一般都是RMI相关线程(RMI RenewClean、 GC Daemon、RMI Reaper),GC线程(Finalizer),引用对象垃圾回收线程(Reference Handler)等系统线程处于这种状态
三、如果很多卡在waiting on monitor
状态,查看锁的持有者在干嘛,使用Analysis -> Monitor Detail
如果有锁就会出现如下锁状态列表:
可以看到线程thread - 507
持有锁,其他11个线程在等待锁
四、没锁的情况时,查看waitting on condition
状态,有很多种场景会使得代码处于这种状态,需要一个个线程去看他们在干嘛,这个没啥规律
五、查看Object.wait
状态
对于应用,很多时候处于这种状态的是在等外部响应,比如等外部接口返回,如果很多线程处于这种状态,要重点看堆栈在干嘛,就可以大概判断你的程序堵在哪了
反而,你不用太过于关注Runnabe
状态的线程,也有可能有处于死锁的:Deadlock,但这种极少
最后说明,通过线程快照不单是看程序有啥问题,还可以看出程序当前是不是到达极限,但这个更多依赖经验,不同应用的线程类型不一样,不同类型的线程处于不同状态的意义得搞清楚,比如数据连接线程,jedis线程,垃圾回收线程等