有没有一种方法可以关闭java.net.http.HttpClient
以立即释放它所持有的资源?
它内部持有一个选择器、一个连接池和一个 Executor
(当使用默认执行器时)。但是它并没有实现Closeable
/AutoCloseable
接口。
有没有一种方法可以关闭java.net.http.HttpClient
以立即释放它所持有的资源?
它内部持有一个选择器、一个连接池和一个 Executor
(当使用默认执行器时)。但是它并没有实现Closeable
/AutoCloseable
接口。
当我重新部署war文件到Tomcat时,遇到了类似的问题。War应用程序有一个HttpClient,它运行计划任务发出http请求并处理结果。
在开发环境中,当重新部署war文件时,我经常会看到来自Tomcat的警告,提示可能会导致内存泄漏的挂起线程。堆栈跟踪指向了HttpClient线程。经过几次尝试,我以以下方式解决了这个问题:
仅在必要时执行作业时才创建HttpClient。它不是作为类或服务的字段创建的,而是作为调度方法内的局部变量创建的。
使用构建器创建HttpClient,并填充ThreadPool Executor,因此我保留了Executor的链接并对其进行控制。
executor = Executors.newSingleThreadExecutor();
HttpClient client = HttpClient.newBuilder().followRedirects(Redirect.ALWAYS).connectTimeout(Duration.ofSeconds(5)).executor(executor).build();
当try-catch块中的作业完成后,finally部分包含以下两行:显式关闭线程池并将null设置为httpClient局部变量:
executor.shutdownNow();
client = null;
System.gc();
注意,设置短连接超时以限制执行时间。保持线程数较小。我使用了1个线程的线程池。
所有这些更改后,Tomcat日志中的关于内存泄漏的警告都消失了。
HttpClient
实现了 AutoCloseable
。它还获得了 shutdown()
、shutdownNow()
和 awaitTermination()
方法,其工作方式类似于 ExecutorService
上的同名方法。
shutdownNow()
应该满足您的需求。如您所见,java.net.http.HttpClient
没有实现Closeable
或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();
}
...
}
除非我别无选择,否则我不会感到舒适使用它。您的引用可能是类型 HttpClient
,因此您需要将其转换为 HttpClientImpl
。依赖于具体实现是不好的,它可能会在未来的版本中发生更改,而不是依赖于 HttpClient
接口。该方法也是私有的。有绕过此限制的方法,但会很混乱。
HttpClient
都会生成一个名为selmgr
的守护线程,该线程应该负责处理在飞行中的请求。当代码中没有对HttpClient
的引用时,这个线程将被关闭。然而,在我的经验中,它并不可靠。特别是当您使用具有future超时的异步方法时。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上进行了测试。
另外,您需要在java命令中添加--add-opens java.net.http/jdk.internal.net.http=ALL-UNNAMED
。
很明显,HttpClient
是设计为自管理的。因此,它负责维护连接池、缓存ttl等。
在HttpClientCode
中,我们可以找到以下代码:
Original Answer翻译成"最初的回答"
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
对象没有被引用时,它会清理所有资源。myExecutorService = Executors.newCachedThreadPool();
HttpClient myHttpClient = HttpClient.newBuilder()
.executor(executor)
....
.build();
正在关闭:
myExecutorService.shutDown();
不必等待客户端断开连接90秒,现在可以“即时”完成。
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();
}
Executor
(如果存在),然后你可以关闭它。 - Jacob G.