1

我在HttpClient基于 Servlet 的 Web 应用程序中使用 Java 的新版本(自版本 11 起):

private static final HttpClient HTTP_CLIENT =
  .connectTimeout(Duration.ofSeconds(5))
  .followRedirects(HttpClient.Redirect.NORMAL)
  .build();

...

public void httpPostAsyncToEndpoint(WebEndpoint endpoint, Map<String,String> params) {
  HttpRequest req = buildRequest(endpoint, params);
  CompletableFuture<HttpResponse<String>> future = HTTP_CLIENT.sendAsync(req, HttpResponse.BodyHandlers.ofString());
  future.thenAccept((HttpResponse<String> res) -> {
    if (res.statusCode() >= 400) {
      if (LOGGER.isErrorEnabled()) {
        LOGGER.error("{} HTTP response returned from endpoint {}", endpoint, res.statusCode());
      }
    }
  }).exceptionally(ex -> {
    if (LOGGER.isErrorEnabled()) {
      LOGGER.error("Could not audit event using endpoint {}", endpoint, ex);
    }
    return null;
  });
}

一切都很好,除了在 Tomcat 上重新启动 Web 应用程序时会产生以下警告:

14-Aug-2020 09:21:16.996 WARNING [http-nio-8080-exec-18] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [MyApp] appears to have started a thread named [HttpClient-3-SelectorManager] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 java.base/sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
 java.base/sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:357)
 java.base/sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:182)
 java.base/sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:124)
 java.base/sun.nio.ch.SelectorImpl.select(SelectorImpl.java:136)
 java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:867)

我怎样才能防止这种情况?我尝试使用ThreadFactory只返回守护线程的自定义:

HttpClient.newBuilder()
  .executor(Executors.newSingleThreadExecutor((Runnable r) -> {
    Thread t = new Thread(r);
    t.setDaemon(true);
    return t;
  }))
  .connectTimeout(Duration.ofSeconds(5))
  .followRedirects(HttpClient.Redirect.NORMAL).build();

但警告仍然存在。

我在 Tomcat 9 上使用 OpenJDK 11.0.7。

4

2 回答 2

2

只要对 的引用仍然存在,HttpClient或者只要客户端发起的操作仍在进行中,选择器管理器线程就会保持活跃。线程可能需要几秒钟才能检测到HttpClient不再引用。所以我不相信你所拥有的是实际泄漏 - 除非持有对静态引用的类HttpClient由于其他原因留在内存中。

于 2020-08-17T12:27:36.787 回答
0

在您的线程上调用 setDaemon 将无济于事,因为 Tomcat 虚拟机没有被关闭。您应该添加代码以尝试清理创建线程的执行程序服务。

将执行程序服务分配给静态变量并shutdown()从 servlet调用destroy()以尝试在取消部署 webapp 时触发清理:

private static final ExecutorService SERVICE = Executors.newSingleThreadExecutor((Runnable r) -> {
    Thread t = new Thread(r);
    t.setDaemon(true);
    return t;
  });

public void destroy() {
    SERVICE.shutdown();
    super.destroy();
}

HttpClient.newBuilder().executor(SERVICE) ...

如果您想完成任何正在进行的任务,您需要决定哪个shutdown()或哪个最好。awaitTermination()

于 2020-08-15T19:03:08.897 回答