【问题标题】:Close Java HTTP Client关闭 Java HTTP 客户端
【发布时间】:2019-05-23 23:43:05
【问题描述】:

有没有办法关闭java.net.http.HttpClient以立即释放它持有的资源?

在内部它包含一个选择器、一个连接池和一个Executor(使用默认值时)。但是它没有实现Closeable/AutoCloseable

【问题讨论】:

  • 我认为它让你做的最多的事情就是获取Executor(如果存在),然后你可以将其关闭。
  • JDK 中的 HttpClientImpl 似乎是一个值得研究的相关地方,但似乎没有公共 API 来管理它,除非您指定自定义 Executor。很少有通过 Http/2 协议进行的相关读取是 When does a http2 TCP connection close?Connection Management

标签: java java-11 java-http-client


【解决方案1】:

我在将war 文件重新部署到Tomcat 时遇到了类似的问题。 War 应用程序有一个 HttpClient,它正在运行发送 http 请求和处理结果的预定作业。

我经常在开发环境中重新部署战争文件时看到来自 Tomcat 的警告,即挂起可能导致内存泄漏的线程。堆栈跟踪指向 HttpClient 线程。经过几次尝试,我以这种方式解决了这个问题:

  1. HttpClient 仅在需要执行作业时创建。它不是作为类或服务的字段创建的,仅作为调度方法中的局部变量。

  2. HttpClient 是使用 builder 创建的,并使用 ThreadPool Executor 填充,因此我保留到 Executor 的链接并对其进行控制。 ExecutorService executor = Executors.newSingleThreadExecutor(); HttpClient client = HttpClient.newBuilder().followRedirects(Redirect.ALWAYS).connectTimeout(Duration.ofSeconds(5)).executor(executor).build();

  3. 当工作在 try-catch 块中完成时,finally 部分有这两行:显式关闭线程池并将 null 设置为 httpClient 局部变量: executor.shutdownNow(); client = null; System.gc();

注意,连接超时时间短以限制执行时间。保持少量线程。我使用 1 个线程的 threadPool。

在所有这些更改之后,有关内存泄漏的警告从 Tomcat 日志中消失了。

【讨论】:

    【解决方案2】:

    如您所见,java.net.http.HttpClient 没有实现 CloseableAutoCloseable。所以我只能想到两个选项,但它们都不是真正的防弹甚至是好的:

    您可以将每个strong reference 消除到您的程序持有的HttpClientrequest a garbage collection。但是,存在一个真正的风险,即超出您的直接控制范围的东西正在抓住它或其组件之一。任何剩余的强引用都会阻止被引用的对象以及它持有强引用的任何对象被垃圾回收。尽管如此,这可以说是比替代方案更惯用的选择。

    我还找到了another option

    final class HttpClientImpl extends HttpClient implements Trackable {
        ...
        // Called from the SelectorManager thread, just before exiting.
        // Clears the HTTP/1.1 and HTTP/2 cache, ensuring that the connections
        // that may be still lingering there are properly closed (and their
        // possibly still opened SocketChannel released).
        private void stop() {
            // Clears HTTP/1.1 cache and close its connections
            connections.stop();
            // Clears HTTP/2 cache and close its connections.
            client2.stop();
        }
        ...
    }
    

    除非我别无选择,否则我不会觉得使用它很舒服。您的参考可能是HttpClient 类型,因此您需要将其转换为HttpClientImpl。依赖具体的实现是不好的,它可能会在未来的版本中改变,而不是HttpClient 接口。该方法也是私有的。有ways around this,但是很乱。

    【讨论】:

      【解决方案3】:

      在 Java 11 中,每个 HttpClient 都会生成一个名为 selmgr 的守护线程,它应该负责处理飞行请求。当代码中没有对HttpClient 的引用时,该线程将被关闭。但是,根据我的经验,它并不可靠。尤其是当您使用具有未来超时的异步方法时。

      这是我使用反射编写的一段代码以可靠地关闭HttpClient

      static void shutDownHttpClient(HttpClient httpClient)
      {
          ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) httpClient.executor().get();
          threadPoolExecutor.shutdown();
          try {
              Field implField = httpClient.getClass().getDeclaredField("impl");
              implField.setAccessible(true);
              Object implObj = implField.get(httpClient);
              Field selmgrField = implObj.getClass().getDeclaredField("selmgr");
              selmgrField.setAccessible(true);
              Object selmgrObj = selmgrField.get(implObj);
              Method shutDownMethod = selmgrObj.getClass().getDeclaredMethod("shutdown");
              shutDownMethod.setAccessible(true);
              shutDownMethod.invoke(selmgrObj);
          }
          catch (Exception e) {
              System.out.println("exception " + e.getMessage());
              e.printStackTrace();
          }
      
      }
      

      如您所见,这是依赖于实现的,可能不适用于未来的 Java 版本。它使用 Java 11 和 Java 12 进行了测试。

      另外,您需要将--add-opens java.net.http/jdk.internal.net.http=ALL-UNNAMED 添加到您的 java 命令中。

      【讨论】:

        【解决方案4】:

        显然HttpClient 被设计为自我管理。所以它负责维护连接池,自己缓存ttl。

        HttpClientCode我们可以找到如下代码:

        if (!owner.isReferenced()) {
                                        Log.logTrace("{0}: {1}",
                                                getName(),
                                                "HttpClient no longer referenced. Exiting...");
                                        return;
                                    }
        

        这是退出SelectorManager循环并清理所有资源的优雅方式。

         @Override
         public void run() {
                    ...
        
                    try {
        
                        while (!Thread.currentThread().isInterrupted()) {
        
                            ...
        
                            if (!owner.isReferenced()) {
                                Log.logTrace("{0}: {1}",
                                        getName(),
                                        "HttpClient no longer referenced. Exiting...");
                                return;
                            }
        
                            ...
                        }
                    } catch (Throwable e) {
                        ...
                    } finally {
                        ...
                        shutdown();
                    }
                }
        
        
        
        
            final boolean isReferenced() {
                    HttpClient facade = facade();
                    return facade != null || referenceCount() > 0;
                }
        

        所以当你的HttpClient 对象不会被引用时,它会清理所有资源。

        UPD:你也应该通过超时来调整你的请求

        【讨论】:

          【解决方案5】:

          如果只是在应用程序生命周期结束时优雅地关闭 HttpClient,System.exit(0) 应该可以正常工作。

          public static void main(String[] args) {
              ...
              System.exit(0);
          }
          

          我认为它会向 JVM 中的所有线程发送一个中断信号,而 HttpClient selmgr 守护进程会自动接收并关闭它。

          final class HttpClientImpl extends HttpClient implements Trackable {
              ...
              // Main loop for this client's selector
              private final static class SelectorManager extends Thread {
                  ...
                  @Override
                  public void run() {
                      ...
                      try {
                          ...
                          while (!Thread.currentThread().isInterrupted()) {...}
                      } catch (Throwable e) {...}
                      finally {
                          ...
                          shutdown();
                      }
          

          【讨论】:

            猜你喜欢
            • 2012-01-13
            • 2012-03-31
            • 2010-10-18
            • 1970-01-01
            • 1970-01-01
            • 2017-10-30
            • 2018-08-11
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多