排查过程

最近某个线上服务经常产生线程数太多的告警,重启之后几个小时就会重现,可以确定肯定是哪里有线程泄露。

记录一次线程泄露排查_线程池

上图是告警时导出的线程栈分析结果,可以看到绝大部分线程都是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这个对象到底被那些对象引用了,通过对象引用找到具体泄露的代码。

记录一次线程泄露排查_apache_02

通过上图的引用关系可以看到,AbstractMultiworkerIOReactor对象最终是由EsclientService持有的,查看对应类的代码可以很容易找到哪里泄露的。

该类有两个create方法,一个构造方法都可以创建RestHighLevelClient和BulkProcessor两个对象,这两个类里面都会持有一个线程池,如果没有关闭的话,持续创建就会导致线程泄露。

结论教训

  1. 线程泄露其实也是对象泄露,定位问题的时候栈信息和对象信息要多关联。
  2. 线程池,连接池等比较重的对象,不要随便暴露公有创建方法,尽量缓存公用,最好有定时清理检测机制。

编辑于 2023-08-17 18:04・IP 属地重庆