Перейти к основному содержимому

Добавление перехватчиков завершения работы для приложений JVM

· 3 мин. чтения

1. Обзор

Как правило, запустить службу несложно. Тем не менее, иногда нам нужно иметь план для изящного закрытия одного из них.

В этом руководстве мы рассмотрим различные способы завершения работы приложения JVM. Затем мы будем использовать API-интерфейсы Java для управления перехватчиками завершения работы JVM. Пожалуйста , обратитесь к этой статье , чтобы узнать больше о завершении работы JVM в приложениях Java.

2. Выключение JVM

JVM можно закрыть двумя способами:

  1. Контролируемый процесс
  2. Резкая манера

Контролируемый процесс останавливает JVM, когда:

  • Последний поток, не являющийся демоном, завершается. Например, когда основной поток завершается, JVM запускает процесс завершения работы.
  • Отправка сигнала прерывания от ОС. Например, нажав Ctrl + C или выйдя из ОС.
  • Вызов System.exit() из кода Java

Хотя мы все стремимся к корректному завершению работы, иногда JVM может завершить работу внезапно и неожиданно. JVM внезапно отключается, когда :

  • Отправка сигнала уничтожения от ОС. Например, выдав kill -9 <jvm_pid>
  • Вызов Runtime.getRuntime().halt() из кода Java
  • ОС хоста неожиданно умирает, например, из-за сбоя питания или паники ОС

3. Крючки отключения

JVM позволяет регистрировать выполнение функций до того, как завершится ее завершение. Эти функции обычно хорошо подходят для высвобождения ресурсов или других подобных хозяйственных задач. В терминологии JVM эти функции называются перехватчиками отключения .

Перехватчики выключения в основном инициализируются, но не запускают потоки . Когда JVM начинает процесс завершения работы, она запускает все зарегистрированные перехватчики в неопределенном порядке. После запуска всех хуков JVM остановится.

3.1. Добавление хуков

Чтобы добавить хук выключения, мы можем использовать метод Runtime.getRuntime().addShutdownHook() :

Thread printingHook = new Thread(() -> System.out.println("In the middle of a shutdown"));
Runtime.getRuntime().addShutdownHook(printingHook);

Здесь мы просто печатаем что-то на стандартный вывод до того, как JVM выключится. Если мы выключим JVM следующим образом:

> System.exit(129);
In the middle of a shutdown

Затем мы увидим, что хук фактически выводит сообщение на стандартный вывод.

JVM отвечает за запуск потоков ловушек . Следовательно, если данный хук уже был запущен, Java выдаст исключение:

Thread longRunningHook = new Thread(() -> {
try {
Thread.sleep(300);
} catch (InterruptedException ignored) {}
});
longRunningHook.start();

assertThatThrownBy(() -> Runtime.getRuntime().addShutdownHook(longRunningHook))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Hook already running");

Очевидно, мы также не можем зарегистрировать хук несколько раз:

Thread unfortunateHook = new Thread(() -> {});
Runtime.getRuntime().addShutdownHook(unfortunateHook);

assertThatThrownBy(() -> Runtime.getRuntime().addShutdownHook(unfortunateHook))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Hook previously registered");

3.2. Удаление крючков

Java предоставляет двойной метод удаления для удаления определенного обработчика завершения работы после его регистрации:

Thread willNotRun = new Thread(() -> System.out.println("Won't run!"));
Runtime.getRuntime().addShutdownHook(willNotRun);

assertThat(Runtime.getRuntime().removeShutdownHook(willNotRun)).isTrue();

Метод removeShutdownHook() возвращает значение true , если ловушка отключения успешно удалена.

3.3. Предостережения

JVM запускает перехватчики выключения только в случае нормального завершения. Таким образом, когда внешняя сила внезапно останавливает процесс JVM, JVM не получает возможности выполнить перехватчики выключения. Кроме того, остановка JVM из кода Java также будет иметь тот же эффект:

Thread haltedHook = new Thread(() -> System.out.println("Halted abruptly"));
Runtime.getRuntime().addShutdownHook(haltedHook);

Runtime.getRuntime().halt(129);

Метод halt принудительно завершает работающую в данный момент JVM. Следовательно, зарегистрированные перехватчики выключения не смогут выполниться.

4. Вывод

В этом руководстве мы рассмотрели различные способы завершения работы приложения JVM. Затем мы использовали несколько API-интерфейсов среды выполнения для регистрации и отмены регистрации обработчиков завершения работы.

Как обычно, пример кода доступен на GitHub .