关闭Java HTTP客户端

k2fxgqgv  于 2023-06-28  发布在  Java
关注(0)|答案(7)|浏览(162)

有没有办法关闭java.net.http.HttpClient以立即释放它所持有的资源?
在内部,它包含一个选择器、一个连接池和一个Executor(当使用默认值时)。但是它不实现Closeable/AutoCloseable

ecfsfe2w

ecfsfe2w1#

我在将一个war文件重新部署到Tomcat中时遇到了类似的问题。War应用程序有一个HttpClient,它正在运行调度的作业,发出http请求并处理结果。
当在开发环境中重新部署war文件时,我经常看到Tomcat的警告,这些警告可能会导致内存泄漏。堆栈跟踪指向HttpClient线程。经过几次尝试,我用这种方式解决了这个问题:

  1. HttpClient仅在需要执行作业时创建。它不是作为类或服务的字段创建的,只是作为scheduled方法中的局部变量。
  2. HttpClient是使用builder创建的,并使用ThreadPool Executor填充,因此我保留了到Executor的链接并对其进行控制。ExecutorService executor = Executors.newSingleThreadExecutor(); HttpClient client = HttpClient.newBuilder().followRedirects(Redirect.ALWAYS).connectTimeout(Duration.ofSeconds(5)).executor(executor).build();
    1.当在try-catch块中完成作业时,finally部分有以下两行:显式关闭线程池,并将httpClient局部变量设置为null:executor.shutdownNow(); client = null; System.gc();
    注意,有一个短的连接超时来限制执行时间。保持线程数量较小。我使用1个线程的threadPool。
    在所有这些更改之后,有关内存泄漏的警告从Tomcat日志中消失了。
e4yzc0pl

e4yzc0pl2#

正如您所注意到的,java.net.http.HttpClient没有实现CloseableAutoCloseable。所以我只能想到两个选择,但它们都不是真正的防弹甚至是好的:
您可以将每个strong reference消除为程序保存的HttpClientrequest a garbage collection。然而,有一个真实的的风险,即超出你直接控制的东西正在抓住它或它的一个组成部分。任何剩余的强引用都将阻止被引用的对象以及它持有强引用的任何对象被垃圾回收。尽管如此,这可以说是比另一种选择更惯用的选择。
我还找到了另一个选择。

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,但它是混乱的。

mbzjlibv

mbzjlibv3#

在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命令中。

4urapxun

4urapxun4#

显然,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:你还应该通过传递超时来调优你的请求

ee7vknir

ee7vknir5#

这有点晚了,但我只想强调一下Jacob G.(2018年12月25日)包含了一个对我有效的解决方案:
创建httpclient:

myExecutorService = Executors.newCachedThreadPool();
HttpClient myHttpClient = HttpClient.newBuilder() 
    .executor(executor) 
    ....
    .build();

关闭:

myExecutorService.shutDown();

而不是必须等待90秒的客户端放弃其连接,它发生“瞬间”。

7fhtutme

7fhtutme6#

如果只是为了在应用程序生命周期结束时优雅地关闭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();
            }
46qrfjad

46qrfjad7#

根据Java 21,HttpClient实现了AutoCloseable。它还获得了方法shutdown()shutdownNow()awaitTermination(),它们的工作方式与ExecutorService上的同名方法类似。
shutdownNow()应该能满足你的需求。

相关问题