如何解决在Tomcat中使用net.http.HttpClient会导致内存泄漏
我正在基于Servlet的Web应用程序中使用Java的新版本(版本11起)HttpClient
:
private static final HttpClient HTTP_CLIENT =
.connectTimeout(Duration.ofSeconds(5))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
...
public void httpPostAsyncToEndpoint(WebEndpoint endpoint,Map<String,String> params) {
HttpRequest req = buildRequest(endpoint,params);
CompletableFuture<HttpResponse<String>> future = HTTP_CLIENT.sendAsync(req,HttpResponse.BodyHandlers.ofString());
future.thenAccept((HttpResponse<String> res) -> {
if (res.statusCode() >= 400) {
if (LOGGER.isErrorEnabled()) {
LOGGER.error("{} HTTP response returned from endpoint {}",endpoint,res.statusCode());
}
}
}).exceptionally(ex -> {
if (LOGGER.isErrorEnabled()) {
LOGGER.error("Could not audit event using endpoint {}",ex);
}
return null;
});
}
一切正常,但是在Tomcat上重新启动Web应用程序时,会产生以下警告:
14-Aug-2020 09:21:16.996 WARNING [http-nio-8080-exec-18] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [MyApp] appears to have started a thread named [HttpClient-3-SelectorManager] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
java.base/sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
java.base/sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:357)
java.base/sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:182)
java.base/sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:124)
java.base/sun.nio.ch.SelectorImpl.select(SelectorImpl.java:136)
java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:867)
如何防止这种情况?我试图使用一个自定义ThreadFactory
,它仅返回守护程序线程:
HttpClient.newBuilder()
.executor(Executors.newSingleThreadExecutor((Runnable r) -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}))
.connectTimeout(Duration.ofSeconds(5))
.followRedirects(HttpClient.Redirect.NORMAL).build();
但警告仍然存在。
我正在Tomcat 9上使用OpenJDK 11.0.7。
解决方法
只要对HttpClient
的引用是活动的,或者只要客户端发起的操作仍在进行,选择器管理器线程就会保持活动。线程可能需要几秒钟的时间才能检测到HttpClient
不再被引用。因此,我不认为您所拥有的是实际的泄漏-除非持有对HttpClient
的静态引用的类由于其他原因而保留在内存中。
由于未关闭Tomcat VM,因此在线程上调用setDaemon将无济于事。您应该添加代码以尝试清理创建线程的执行程序服务。
将执行程序服务分配给静态变量,并从servlet shutdown()
调用destroy()
来尝试在取消部署Web应用程序时触发清理:
private static final ExecutorService SERVICE = Executors.newSingleThreadExecutor((Runnable r) -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
});
public void destroy() {
SERVICE.shutdown();
super.destroy();
}
HttpClient.newBuilder().executor(SERVICE) ...
如果您要完成任何正在进行的任务,则需要确定shutdown()
或awaitTermination()
中哪个最好。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。