Закрыть Java HTTP-клиент

Есть ли способ закрыть java.net.http.HttpClient мгновенно высвободить имеющиеся у него ресурсы?

Внутри он содержит селектор, пул соединений и Executor (при использовании по умолчанию). Однако это не реализует Closeable/AutoCloseable,

7 ответов

У меня была аналогичная проблема, когда я повторно развертывал файл войны в Tomcat. У приложения War был HttpClient, который выполнял запланированные задания, отправляя HTTP-запросы и обрабатывая результаты.

Я довольно часто получал предупреждения от Tomcat при повторном развертывании файла war на сайте разработчиков о зависании потоков, которые могут вызвать утечку памяти. Трассировка стека указывала на потоки HttpClient. После нескольких попыток я решил эту проблему следующим образом:

  1. HttpClient создается только тогда, когда это необходимо для выполнения задания. Он не создается как поле класса или serivec, а только как локальная переменная внутри запланированного метода.

  2. HttpClient создается с помощью построителя и заполняется 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();

Примечания, имейте короткий тайм-аут соединения, чтобы ограничить время выполнения. держите количество потоков небольшим. Я использую 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();
            }
Другие вопросы по тегам