【性能调优】线程快照分析

在性能调优的时候,apm监控可以看到一直卡在mysql的DruidDataSource.getConnection()
在这里插入图片描述
这个时候数据库的资源使用情况cpu、内存、网络均正常,dubbo容器的cpu等资源也正常
抓取线程快照查看线程为何一直处于数据库等getConnection

获取线程快照

在服务端连续三次将线程快照down下来,命令如下

jstack pid > 文件名.txt
  1. pid为需要抓取的进程数
  2. 抓线程快照,必须是在加压,且压到出现问题时抓取,连续抓取3~5次

有两种方式可以分析线程快照

  1. 直接使用文本的方式
  2. 使用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

  1. 线程状态waiting for monitor entry
    意味着在等待进入一个临界区 ,所以它在"Entry Set"队列中等待
    此时线程状态一般都是 Blocked:java.lang.Thread.State: BLOCKED (on object monitor)

如果大量线程在这个状态,可能是一个全局锁阻塞住了大量线程
如果短时间内打印的 thread dump 文件反映,随着时间流逝,waiting for monitor entry 的线程越来越多,没有减少的趋势,可能意味着某些线程在临界区里呆的时间太长了,以至于越来越多新线程迟迟无法进入临界区

  1. 线程状态waiting on condition
    说明它在等待另一个条件的发生,来把自己唤醒,或者干脆它是调用了 sleep(N)
    此时线程状态大致为以下几种:
    java.lang.Thread.State: WAITING (parking):一直等那个条件发生
    java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定时的,那个条件不到来,也将定时唤醒自己

如果大量线程在在这个状态,可能是它们又跑去获取第三方资源,尤其是第三方网络资源,迟迟获取不到Response,导致大量线程进入等待状态
所以如果发现有大量的线程都处在 Wait on condition,从线程堆栈看,正等待网络读写,这可能是一个网络瓶颈的征兆,因为网络阻塞导致线程无法执行

  1. 线程状态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线程,垃圾回收线程等

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章