我有一个小型应用程序,它使用 Jetty v9.2, HttpClient简单地轮询服务器。几天后,应用程序将冻结。最初,我们确定需要增加线程池的大小以减轻性能损失。该更改在几天内恢复了性能。锁定仍然存在。原因已被隔离到 HTTP GET 调用(如果我们注释掉该方法,问题就会消失)。
出现在Jetty HttpClient Connection management或Thread management的根本原因。通常 Jetty HttpClient 会创建一组线程来处理 HTTP GET(见下文),这些线程会按照您的预期启动和消失。大约 40 小时或运行后,JDK VisualVM 显示至少 9 个连接线程不会立即消失:
- HttpClient - 调度器 x 1
- HttpClient - 选择器客户端 SecectorManager x 4
- HttpClient x 4
还
- RMI TCP 连接
总共九或十个线程。在下一次读取时,会创建新的线程实例来承载负载并且客户端继续。此外应用程序。有一个带有专用线程的时钟,在应用程序锁定后继续运行,这表明 JVM、操作系统和机器本身都很好。
有时,我们会看到这些“卡住”的线程会停留长达一个小时,然后才会退出 VisualVM 线程显示。至少 36 小时后,我们看到线程仍然存在并且我们没有看到它们消失。
足够的天数后,软件锁定。指示的解释是未清理的线程实例泄漏。它出现了应用程序。线程用完了,不能做更多的工作。它肯定会阻止服务器日志所见证的 HTTP GET。
主要的 HTTP 调用使用下面的代码,HttpClient GET 方法:
/**
* GET
* @return null or string returned from server
**/
public static String get( final String command ){
String rslt = null;
final String reqStr = "http://www.google.com"; // (any url)
HttpClient httpClient = new HttpClient();
Request request;
ContentResponse response;
try {
//-- Start HttpClient
httpClient.start();
request = httpClient.newRequest( reqStr );
response = request.send();
if( null == response ){
LOG.error( "NULL returned from previous HTTP request.");
}
else {
if( (501 == response.getStatus()) || (502 == response.getStatus()) ){
setNetworkUnavailable(String.format("HTTP Server error: %d", response.getStatus() ));
}
else {
if( 404 == response.getStatus() ){
Util.puts(LOG,"HTTP Server error: 404");
// ignore message since we are talking to an old server
}
else if( 200 == response.getStatus() ){
rslt = response.getContentAsString();
}
else {
LOG.error(String.format( " * Response status: \"%03d\".", response.getStatus() ));
}
setNetworkAvailable();
}
}
}
catch ( InterruptedException iEx ){
LOG.warn( "InterruptException processing: "+reqStr, iEx );
}
catch ( Exception ex ){
Throwable cause = eEx.getCause();
if( (cause instanceof NoRouteToHostException) ||
(cause instanceof EOFException) ||
(cause instanceof SocketException)
&& cause.getMessage().startsWith( EX_NETWORK_UNREACHABLE ) ){
setNetworkUnavailable( cause.getMessage() );
}
else {
LOG.error( "Exception on: "+command, ex );
}
}
finally {
try {
httpClient.stop();
}
catch ( Exception ex ){
LOG.error( "Exception httpClient.stop(), ServerManager::get()", ex );
}
}
return rslt;
}//get method
这是基于简单的示例,关于使用 HttpClient 的细节很少。一切都是按照霍伊尔做的吗?
在不同的执行运行中,我们还会看到以下异常和日志消息:
- [36822522] WARN 2014-Sep-02 02:46:28.464> HttpClient@2116772232{STOPPING,8<=0<=200,i=0,q=0} 无法停止线程 [HttpClient@2116772232-729770,5 ,]
我们想知道这条消息是否与其中一个卡住的线程有关?或者,此消息是否表明我们需要检查一个单独且不同的问题?还:
- java.util.concurrent.TimeoutException (ExecutionException)
这似乎是线程超时异常。哪个线程?这是否与他的 HTTP 连接线程有关?我认为,当服务在内部捕获错误时,它们至少可以指示错误的位置和堆栈跟踪。
有一些明显的问题:
- get() 方法代码是否按要求编写,不存在泄漏或为 Jetty HttpClient 代码留下挂起的资源?
- 我们如何捕捉警告:“无法停止线程”错误?
- 这个错误有什么影响?有没有办法“粉碎”这样卡住的线程?
- 这是否与 10 个悬挂连接线程有关?只有一条警告信息。
- 可以想象一个挂起的线程需要一个错误标签,而不是一个警告。
- Jetty HttpClient 中是否有捕获线程错误和一般错误的过程?
- HttpClient 可以使用哪些属性来调整服务?
- 是否有我们可以用来直接影响线程锁定的设置?
- HttpClient 的环境或上下文中可以使用哪些属性来控制调整服务?
- Jetty HttpClient 是否可以重新启动/重新启动或只是停止?
- Jetty 调用仅在所示的 GET 方法中进行(尽管有更多的日志记录等)
- RMI 线程是否是 Jetty HttpClient 调用的一部分?
另一个观察结果是,当我们在 VisualVM 中“卡住”线程时,它会在“线程”面板中显示过多的守护程序线程,而不是非守护程序线程的增加。
通过在 for 循环中运行上面显示的代码大约 3 或 4 小时,在 HttpClient send() 调用之间有 250 毫秒的中断显示线程泄漏——在 Linux 上重现很简单。日志输出显示没有 WARNings 并且在距离线程泄漏至少 30 分钟的网络上只有两个超时错误。
欢迎提出建议、观察、改进和回答。我们提前致谢。
相关问题:
这些问题涵盖了一些非常相似的点