又一次线上救火,线上再次发生线程数暴涨,且无法回收,本文记录问题发现解决的全过程。

问题说明
某系统内存使用过大,并发现线程数很高,有一万多个(通过监控系统就可以发现,细节此处不赘述)。

详细排查过程

说明:也可以使用jdk命令,获取thread dump信息,使用命令简单处理,即可获得数量最多线程。
jstack 1 > thread.dump // 1 为java进程id, 重定向到thread.dump文件
cat thread.dump|grep tid|sort //过滤tid,并排序"idle-connection-evictor-1" #941 daemon prio=5 os_prio=0 cpu=0.77ms elapsed=202.60s tid=0x00007f78ec521750 nid=0x3af waiting on condition [0x00007f787d574000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(java.base@17/Native Method)
at java.lang.Thread.sleep(java.base@17/Thread.java:337)
at java.util.concurrent.TimeUnit.sleep(java.base@17/TimeUnit.java:446)
at org.apache.hc.core5.util.TimeValue.sleep(TimeValue.java:323)
at org.apache.hc.client5.http.impl.IdleConnectionEvictor$1.run(IdleConnectionEvictor.java:62)
at java.lang.Thread.run(java.base@17/Thread.java:833)分析上面的调用栈,第7行,可见问题来自httpclient5的IdleConnectionEvictor类。


底层代码,设置线程池

代码分析:
每次执行方法,都会创建一个httpclient,且第三方工具,未提供close方式,不能将httpclient 关闭(httpclient里面有一个叫做ideConnectionEvitor的类,它有一个thread),这样就造成每次执行都会生成一个名字为idle-connection-evitor-1的thread,且不会被释放。
按照appid将httpclient缓存起来,每次都执行同一个httpclient。

套路总结
遇到线程数过多,排查套路如下:
1.先找到数目最多的线程
可以通过arms(公司使用的阿里云监控系统--读者可结合自己公司实际情况)去找,如果arms不好用,请运维同学执行一下jstack,把线程dump出来
jstack pid|grep tid|sort // pid 是java进程的id2.再找到线程栈信息,找到调用链路,确认业务代码。
如果线程栈里面显示的是调用的root方法,不是业务方法,可以先找到对应的class,再确认上层调用。本文中,root方法是thread.sleep(), 不能确定业务方法,但是没关系,我们找到了httpclient5,根据pom依赖,又找到了上层调用是:yidun-java-sdk。
3.根据线程名字等信息,找到对应的thread,再结合业务情况判断修改。
找到线程的名字,再根据线程的名字定位到代码,是发现问题的关键!所以写多线程的代码,使用线程池,务必要指定线程池的名字,不能使用默认的名字,因为一个有意义的名字可以就可以给排查工作节省大量的时间!
