记一次排查服务器线程数异常的过程
某机器最近突然线程数持续上升,现在已经达到了6000多,肯定是有问题的。对比该服务器和其他服务器上部署项目的不同,发现某机器上独有的项目是最近新上的A项目,并且当该项目重启之后,线程数会降低至1200。所以我们确定了是A项目出现了问题,开始针对该项目进行分析。
查看线程数的命令:
pstree -p | wc -l
pstree -p <pid> 查看该进程下的所有线程
1.查询tomcat的pid
ps aux |grep A项目
2.通过pid得到该项目的堆栈日志,并保存到文件中
jstack -F pid >
3.分析堆栈日志,得出block的线程达到了5700,这种情况明显是异常的,一条完整的block日志记录如下:
Thread 11463: (state = BLOCKED)
- (long) @bci=0 (Compiled frame; information may be imprecise)
- $() @bci=16, line=66 (Interpreted frame)
- () @bci=11, line=748 (Interpreted frame)
4.分析block
在这部分走了很大的弯路,因为A项目使用了我们之前没使用过的netty。所以在最开始猜想错误原因时就往netty的方向去想。导致浪费了很多时间。
分析blocked,如上,是出现在IdleConnectionEvictor的错误,这个类是httpclient包下的,本来看到这我就应该定位到跟netty没关系,但无奈对netty了解不够深入,没有确认到这一点。
可参考这篇文章: /article/
大意是当我们使用httpclient时,往往会启动一个空闲链接回收的线程,代码如下:
= () .setMaxConnTotal(MAX_CONN_TOTAL) .setMaxConnPerRoute(MAX_CON_PER_ROUTE) .setDefaultCookieStore(cookieStore) .evictExpiredConnections() .evictIdleConnections(30, )
也就是说,每new一个httpclient,就会多创建一个idleconnection线程。当你的自定义的client类被GC回收时,是不会主动将这个线程也回收掉的。所以就导致该线程一直处于blocked状态。既不会有人调用,也不会有人销毁。
那我们怎么解决这个问题呢?
其实思路很简单,就是idleconnection线程只保存一份就可以了。
针对这个有两种做法,
(1) 将httpclient变为static
static {
Registry<ConnectionSocketFactory> registry = ().register("http", ()).register("https", ()).build();
PoolingHttpClientConnectionManager gcm = new PoolingHttpClientConnectionManager(registry);
(400);
(200);
RequestConfig requestConfig = ().setConnectTimeout(5000).setSocketTimeout(30000).setConnectionRequestTimeout(5000).build();
HttpClientBuilder httpClientBuilder = ();
httpClient = (gcm).setDefaultRequestConfig(requestConfig).setDefaultSocketConfig(().setSoTimeout(30000).build()).evictExpiredConnections().evictIdleConnections(30L, ).build();
defaultHeaders = new Header[]{new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"), new BasicHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"), new BasicHeader("Accept-Encoding", "gzip, deflate"), new BasicHeader("Accept-Language", "zh-CN,zh;q=0.9"), new BasicHeader("Connection", "keep-alive"), new BasicHeader("Upgrade-Insecure-Requests", "1")};
}
(2) 每次都创建一个新的,但是当自定义的client类被回收时,手动将httpClient涉及的东西都关闭
public void destroy() {
if (httpClient != null) {
try {
();
} catch (IOException e) {
("browserHttpClient close exception", e);
}
}
}
@Override
protected void finalize() throws Throwable {
try {
destroy();
} finally {
();
}
}
finalize相关的文章:/a4171175/article/details/90749839
关于使用finalize的争议,可能存在destroy方法执行不完的情况,但根据我们的实际应用,并没有出现大批量的idleconnection线程无法回收的问题。如果您有更好的处理方式,望指教。