21

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

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

4

5 回答 5

5

当我将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 个线程的线程池。

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

于 2020-07-04T22:40:52.883 回答
3

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

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

我还找到了另一种选择

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();
    }
    ...
}

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

于 2018-12-25T07:38:59.107 回答
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 命令。

于 2019-08-12T14:59:10.623 回答
2

显然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:您还应该通过超时来调整您的请求

于 2019-08-12T17:27:50.863 回答
1

如果只是在应用程序生命周期结束时优雅地关闭 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();
            }
于 2021-05-27T05:25:19.463 回答