排查过程
最近某个线上服务经常产生线程数太多的告警,重启之后几个小时就会重现,可以确定肯定是哪里有线程泄露。
上图是告警时导出的线程栈分析结果,可以看到绝大部分线程都是I/O dispatcher线程,查看具体的栈信息如下:
"I/O dispatcher 3480" #1455666 prio=5 os_prio=0 tid=0x00007fc854033800 nid=0x186c runnable [0x00007fc5f39fa000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x00000006b46037f8> (a sun.nio.ch.Util$3)
- locked <0x00000006b4603808> (a java.util.Collections$UnmodifiableSet)
- locked <0x00000006b46037b0> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:255)
at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:591)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
主要是httpclient的io处理线程,但是工程里面有很多地方用到了httpclient,还有很多第三方框架也用到了httpclient,现在的问题是怎么确定到底是哪里泄露的,直接查找代码肯定不现实。
线程泄露其实也就是线程对象的泄露,可以通过关联对象找到泄露的地方,这里我们找一下AbstractMultiworkerIOReactor这个对象到底被那些对象引用了,通过对象引用找到具体泄露的代码。
通过上图的引用关系可以看到,AbstractMultiworkerIOReactor对象最终是由EsclientService持有的,查看对应类的代码可以很容易找到哪里泄露的。
该类有两个create方法,一个构造方法都可以创建RestHighLevelClient和BulkProcessor两个对象,这两个类里面都会持有一个线程池,如果没有关闭的话,持续创建就会导致线程泄露。
结论教训
- 线程泄露其实也是对象泄露,定位问题的时候栈信息和对象信息要多关联。
- 线程池,连接池等比较重的对象,不要随便暴露公有创建方法,尽量缓存公用,最好有定时清理检测机制。
编辑于 2023-08-17 18:04・IP 属地重庆