您好,欢迎访问一九零五行业门户网

Java线程泄露的分析与处理

1. 生产环境的异常现象及初步分析
最近发现系统程序内存消耗越来越大,开始并没特别注意,就简单调了一下jvm参数。但直到前些天内存爆满,持续full gc,这肯定出现了内存泄露。
原以为哪里出现了比较低级的错误,所以很直接想到先去看看程序是在跑哪段代码。jstack -l 以后,居然有上千个线程,而且都是属于running并wait的状态。
i/o dispatcher 125 #739 prio=5 os_prio=0 tid=0x0000000002394800 nid=0x1e2a runnable [0x00007f5c2125b000] 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:79) at sun.nio.ch.selectorimpl.lockanddoselect(selectorimpl.java:86) - locked (a sun.nio.ch.util$2) - locked (a java.util.collections$unmodifiableset) - locked (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:257) at org.apache.http.impl.nio.reactor.baseioreactor.execute(baseioreactor.java:106) at org.apache.http.impl.nio.reactor.abstractmultiworkerioreactor$worker.run(abstractmultiworkerioreactor.java:590) at java.lang.thread.run(thread.java:745) locked ownable synchronizers: - none pool-224-thread-1 #738 prio=5 os_prio=0 tid=0x00007f5c463f4000 nid=0x1e29 runnable [0x00007f5c2024b000] 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:79) at sun.nio.ch.selectorimpl.lockanddoselect(selectorimpl.java:86) - locked (a sun.nio.ch.util$2) - locked (a java.util.collections$unmodifiableset) - locked (a sun.nio.ch.epollselectorimpl) at sun.nio.ch.selectorimpl.select(selectorimpl.java:97) at org.apache.http.impl.nio.reactor.abstractmultiworkerioreactor.execute(abstractmultiworkerioreactor.java:342) at org.apache.http.impl.nio.conn.poolingnhttpclientconnectionmanager.execute(poolingnhttpclientconnectionmanager.java:191) at org.apache.http.impl.nio.client.closeablehttpasyncclientbase$1.run(closeablehttpasyncclientbase.java:64) at java.lang.thread.run(thread.java:745) locked ownable synchronizers: - none
我以下的思考路径都未能解决(自己记录一下,看官可以跳过...)
查看线程的stack,看调用处是否有问题。这个一般都能解决问题,但是上面的异常线程栈确实没什么信息量,无法定位。
google了一下有关大量这个线程停在epollwait的资料,发现这个现象和epoll nio的bug是一样的,还以为碰到了一个无法处理的高级问题。第一反应就是去httpclient的官网查bug日志,结果还真发现了最近的升级有解决类似问题的,然后升级到最新版问题依旧。但是最后仔细想想,也确实不太可能,毕竟应用场景还是比较普通的。
jmap -histo 看了一下对象,结果发现存在internalhttpasyncclient数量和泄露的线程数量刚好相等,所以基本就确定是这个对象的创建和回收有问题。但是这是谁创建的?
查了调用栈和异常对象的package,发现是httpclient的,把本地所有相关调用都查了一遍,看起来写的也都是对的。
搬出jvirtualvm的性能分析工具,发现只能看到泄露现象,无法定位问题。
这下懵逼了,刚好忙其他事,就放了几天顺带考虑一下,还好泄露比较慢,问题处理不着急。。。
2. 线程泄露的分析方法
处理这个问题的关键:必须准确知道是什么泄露了线程!
在google过程中突然受到启发,jdk中的工具是应该可以分析引用的。最后发现jhat - java heap analysis tool正是我要的。
最终解决方式:
jmap -f -dump:format=b,file=tomcat.bin 导出tomcat的内存
jhat -j-xmx4g 分析heap中的信息(注意:分析非常消耗cpu和内存,尽量在配置较好的机器上运行)
查看相关对象的reference,oql也可以用,但是网页版直接点链接也够用了。
3. 锁定原因并解决
从之前异常heap中发现存在的问题对象有如下这些:
$ cat histo | grep org.apache.http. | grep 1944 | less 197: 1944 217728 org.apache.http.impl.nio.conn.managednhttpclientconnectionimpl 232: 1944 171072 org.apache.http.impl.nio.conn.cpool 233: 1944 171072 org.apache.http.impl.nio.reactor.defaultconnectingioreactor 248: 1944 155520 org.apache.http.impl.nio.reactor.baseioreactor 249: 1944 155520 org.apache.http.impl.nio.reactor.iosessionimpl 276: 1944 139968 org.apache.http.impl.nio.client.internalhttpasyncclient 277: 1944 139968 org.apache.http.impl.nio.conn.cpoolentry 323: 1944 108864 org.apache.http.impl.nio.client.mainclientexec 363: 1944 93312 org.apache.http.impl.nio.codecs.defaulthttpresponseparser 401: 1944 77760 org.apache.http.impl.nio.reactor.sessioninputbufferimpl 402: 1944 77760 org.apache.http.impl.nio.reactor.sessionoutputbufferimpl 403: 1944 77760 org.apache.http.nio.protocol.httpasyncrequestexecutor$state 442: 1944 62208 org.apache.http.impl.cookie.defaultcookiespecprovider 443: 1944 62208 org.apache.http.impl.nio.conn.poolingnhttpclientconnectionmanager 444: 1944 62208 org.apache.http.nio.conn.ssl.ssliosessionstrategy 445: 1944 62208 org.apache.http.nio.pool.abstractnioconnpool$2 511: 1944 46656 [lorg.apache.http.impl.nio.reactor.abstractmultiworkerioreactor$worker; 512: 1944 46656 [lorg.apache.http.impl.nio.reactor.baseioreactor; 513: 1944 46656 org.apache.http.conn.ssl.defaulthostnameverifier 514: 1944 46656 org.apache.http.impl.cookie.defaultcookiespec 515: 1944 46656 org.apache.http.impl.cookie.netscapedraftspecprovider 516: 1944 46656 org.apache.http.impl.nio.client.closeablehttpasyncclientbase$1 517: 1944 46656 org.apache.http.impl.nio.client.internaliodispatch 518: 1944 46656 org.apache.http.impl.nio.codecs.defaulthttprequestwriter 519: 1944 46656 org.apache.http.impl.nio.conn.poolingnhttpclientconnectionmanager$configdata 520: 1944 46656 org.apache.http.impl.nio.conn.poolingnhttpclientconnectionmanager$internaladdressresolver 521: 1944 46656 org.apache.http.impl.nio.conn.poolingnhttpclientconnectionmanager$internalconnectionfactory 522: 1944 46656 org.apache.http.impl.nio.reactor.abstractmultiworkerioreactor$worker 523: 1944 46656 org.apache.http.nio.protocol.httpasyncrequestexecutor 603: 1944 31104 org.apache.http.client.protocol.requestexpectcontinue 604: 1944 31104 org.apache.http.conn.routing.basicroutedirector 605: 1944 31104 org.apache.http.impl.auth.httpauthenticator 606: 1944 31104 org.apache.http.impl.conn.defaultrouteplanner 607: 1944 31104 org.apache.http.impl.cookie.ignorespecprovider 608: 1944 31104 org.apache.http.impl.nio.sessionhttpcontext 609: 1944 31104 org.apache.http.impl.nio.reactor.abstractioreactor$1 610: 1944 31104 org.apache.http.impl.nio.reactor.abstractmultiworkerioreacto
接下来要找出到底谁new了这些对象,这些异常object中很多是内部field,所以要先找出最外层的对象。这个就只是边猜边看了,结果发现就是internalhttpasyncclient。点开进去看了一下,发现有一堆instance,最后了发现泄露的对象。也可以用oql select referrers(c) from org.apache.http.impl.nio.client.internalhttpasyncclient c
instance of org.apache.http.impl.nio.client.internalhttpasyncclient@0x932be638 (128 bytes) class: class org.apache.http.impl.nio.client.internalhttpasyncclientinstance data members: ... references to this object: org.apache.http.impl.nio.client.closeablehttpasyncclientbase$1@0x932be6c8 (40 bytes) : field this$0 com.aliyun.mqs.common.http.defaultserviceclient@0x931cc588 (32 bytes) : field httpclient
这里的信息就是阿里云的mqs创建了这些对象。去看了一下代码,书写看似没有问题,实际上,连接压根忘记关了。有问题的阿里云mqs文档是这个,但是最新版本的官网文档已经改用了org.eclipse.jetty.client.httpclient,也是没有显式调用stop函数,希望这个类库不会出现此问题。
@service public class aliyunservice implements ialiyunservice { private static logger logger = logger.getlogger(aliyunservice.class.getname()); @autowired private aliyunconfig aliyunconfig; @override public void sendmessage(string content) { mqsclient client = new defaultmqsclient(aliyunconfig.mqendpoint, aliyunconfig.mqaccessid, aliyunconfig.mqaccesskey); string queuename = aliyunconfig.mqqueue; try { cloudqueue queue = client.getqueueref(queuename); // queue没做关闭处理,应该最后加上 // finally{ queue.close(); } message message = new message(); message.setmessagebody(content); queue.putmessage(message); } catch (exception e) { logger.warning(e.getmessage()); } } }
以下是mqs给的jar中相应关闭的源码
public final class cloudqueue { private serviceclient serviceclient; ... public void close() { if(this.serviceclient != null) { this.serviceclient.close(); } } }
真相大白!至此修改后,问题顺利解决。
4. 总结
首先,这个问题的解决确实还是要善用并熟悉jdk工具*,之前对jhat的理解不深,导致第一时间没有想到这个解决方案。日后再有内存问题,会有更犀利的解决方法了。
其次,熟悉了线程泄露的现象,解决方式还是去找线程的对象,说到底,还是对象的泄露。
最后,真的要吐槽一下阿里,我之前接阿里百川就恶心的不行,这次又出现低级错误。我一直认为阿里是中国软件技术最好的公司,但基层研发的是水平真心一般,研发质量控制有问题啊
其它类似信息

推荐信息