Закрыть Java HTTP-клиент
Есть ли способ закрыть java.net.http.HttpClient
мгновенно высвободить имеющиеся у него ресурсы?
Внутри он содержит селектор, пул соединений и Executor
(при использовании по умолчанию). Однако это не реализует Closeable
/AutoCloseable
,
7 ответов
У меня была аналогичная проблема, когда я повторно развертывал файл войны в Tomcat. У приложения War был HttpClient, который выполнял запланированные задания, отправляя HTTP-запросы и обрабатывая результаты.
Я довольно часто получал предупреждения от Tomcat при повторном развертывании файла war на сайте разработчиков о зависании потоков, которые могут вызвать утечку памяти. Трассировка стека указывала на потоки 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, в разделе finally есть две строки, которые: явно закрывают пул потоков и устанавливают значение null для локальной переменной httpClient:
executor.shutdownNow(); client = null; System.gc();
Примечания, имейте короткий тайм-аут соединения, чтобы ограничить время выполнения. держите количество потоков небольшим. Я использую threadPool из 1 потока.
После всех этих изменений из журналов Tomcat исчезли предупреждения об утечках памяти.
В 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.
Согласно Java 21,HttpClient
реализуетAutoCloseable
. Он также получает методыshutdown()
, иawaitTermination()
, которые работают аналогично одноименным методам наExecutorService
.
shutdownNow()
должен удовлетворить ваши потребности.
Как вы заметили, java.net.http.HttpClient
не реализует Closeable
или же AutoCloseable
, Таким образом, я могу думать только о 2 вариантах, но ни один из них не является действительно пуленепробиваемым или даже хорошим:
Вы можете исключить все сильные ссылки на 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
предназначен для самостоятельного управления. Таким образом, он отвечает за поддержку пула соединений, сам кеширует 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: также вы должны настраивать свои запросы, передавая таймауты
Это немного поздно, но я просто хочу подчеркнуть, что комментарий Джейкоба Г. (25 декабря 2018 г.) содержал решение, которое сработало для меня:
Создание http-клиента:
myExecutorService = Executors.newCachedThreadPool();
HttpClient myHttpClient = HttpClient.newBuilder()
.executor(executor)
....
.build();
Выключение:
myExecutorService.shutDown();
Вместо того, чтобы ждать 90 секунд, пока клиент разорвет соединение, это произошло «мгновенно».
Если нужно просто изящно закрыть 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();
}