是否可以关闭java.net.http.HttpClient
来立即释放其拥有的资源?
在内部,它包含一个选择器,一个连接池和一个Executor
(使用默认值时)。但是,它没有实现Closeable
/ AutoCloseable
。
答案 0 :(得分:1)
您已经注意到,java.net.http.HttpClient
没有实现Closeable
或AutoCloseable
。所以我只能想到2个选项,但它们都不是真正的防弹甚至是不错的:
您可以消除程序所持有的HttpClient
和strong reference中的每个request 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,但很乱。
答案 1 :(得分:1)
在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进行了测试。
此外,您需要向Java命令中添加--add-opens java.net.http/jdk.internal.net.http=ALL-UNNAMED
。
答案 2 :(得分:1)
很显然,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:您还应该通过传递超时来调整请求
答案 3 :(得分:1)
当我将战争文件重新部署到Tomcat时,我遇到了类似的问题。战争应用程序有一个HttpClient,它正在运行计划的作业,这些作业发出HTTP请求并处理结果。
当在开发环境中频繁挂载可能导致内存泄漏的挂线程时,我经常从Tomcat中收到警告。堆栈跟踪指向HttpClient线程。经过几次尝试,我以这种方式解决了这个问题:
HttpClient仅在需要执行作业时创建。它不会创建为类或serivec的字段,而只会创建为计划方法内部的局部变量。
HttpClient是使用构建器创建的,并使用ThreadPool Executor填充,因此我保留了对Executor的链接并对其进行控制。
ExecutorService executor = Executors.newSingleThreadExecutor();
HttpClient client = HttpClient.newBuilder().followRedirects(Redirect.ALWAYS).connectTimeout(Duration.ofSeconds(5)).executor(executor).build();
在try-catch块中完成作业时,最后一节具有以下两行:显式关闭线程池,并将httpClient局部变量设置为null:
executor.shutdownNow();
client = null;
System.gc();
注意,连接超时时间短以限制执行时间。保持线程数量少。我使用1个线程的threadPool。
所有这些更改之后,有关内存泄漏的警告从Tomcat日志中消失了。
答案 4 :(得分:0)
如果只是在应用程序生命周期结束时优雅地关闭 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();
}