2

我有一个小型应用程序,它使用 Jetty v9.2, HttpClient简单地轮询服务器。几天后,应用程序将冻结。最初,我们确定需要增加线程池的大小以减轻性能损失。该更改在几天内恢复了性能。锁定仍然存在。原因已被隔离到 HTTP GET 调用(如果我们注释掉该方法,问题就会消失)。

出现在Jetty HttpClient Connection managementThread 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 连接线程有关?我认为,当服务在内部捕获错误时,它们至少可以指示错误的位置和堆栈跟踪。

有一些明显的问题:

  1. get() 方法代码是否按要求编写,不存在泄漏或为 Jetty HttpClient 代码留下挂起的资源?
  2. 我们如何捕捉警告:“无法停止线程”错误?
    • 这个错误有什么影响?有没有办法“粉碎”这样卡住的线程?
    • 这是否与 10 个悬挂连接线程有关?只有一条警告信息。
    • 可以想象一个挂起的线程需要一个错误标签,而不是一个警告。
  3. Jetty HttpClient 中是否有捕获线程错误和一般错误的过程?
  4. HttpClient 可以使用哪些属性来调整服务?
    • 是否有我们可以用来直接影响线程锁定的设置?
  5. HttpClient 的环境或上下文中可以使用哪些属性来控制调整服务?
  6. Jetty HttpClient 是否可以重新启动/重新启动或只是停止?
    • Jetty 调用仅在所示的 GET 方法中进行(尽管有更多的日志记录等)
  7. RMI 线程是否是 Jetty HttpClient 调用的一部分?

另一个观察结果是,当我们在 VisualVM 中“卡住”线程时,它会在“线程”面板中显示过多的守护程序线程,而不是非守护程序线程的增加。

通过在 for 循环中运行上面显示的代码大约 3 或 4 小时,在 HttpClient send() 调用之间有 250 毫秒的中断显示线程泄漏——在 Linux 上重现很简单。日志输出显示没有 WARNings 并且在距离线程泄漏至少 30 分钟的网络上只有两个超时错误。

欢迎提出建议、观察、改进和回答。我们提前致谢。

相关问题

这些问题涵盖了一些非常相似的点

4

1 回答 1

2

这种情况似乎可以通过确保两件事来解决。

  1. 确保应用程序的线程池中有足够的线程
  2. 确保使用 Jetty 清理代码并捕获/管理所有异常。

这两个动作是相互关联的。如果有时 HttpClient 错过异常或错误,则线程会挂起。似乎避免这种情况的唯一方法是确保使用的每个 HttpCLient 都调用 HttpCLient.stop()。这需要放在finally {...}子句中。

其次,异步调用必须在调用 HttpCLient.stop() 之前等待 CompleteListener。这似乎是确保停止“干净”完成的唯一方法。在某些情况下,stop() 调用似乎可以正常进行。最终有些会导致异常,您的应用程序会慢慢泄漏资源。外观就像 JVM 已冻结,但一些非守护程序任务可能会继续(例如 GUI 线程),并且您可能不会注意到问题,直到 PC 本身耗尽资源或崩溃。这是一个极端的案例## Heading ##running 数周。

此处显示了适当关闭 HttpClient 的可靠示例:

线程数将取决于您的应用程序。我建议在调整线程池中的线程数之前使用jVisualVM或类似的东西来确保您的 Jetty 线程都正确清理。

我觉得文档需要强调清理并确保调用 stop()。据我所知,如何结束异步调用的信息没有记录。只要您的 Jetty 调用干净地停止,那么提供足够的线程似乎可以解决这个问题——使用通常的警告来管理并发性。

于 2014-10-29T23:11:30.153 回答