Как изящно обрабатывать сигнал SIGKILL в Java
Как вы справляетесь с очисткой, когда программа получает сигнал уничтожения?
Например, есть приложение, к которому я подключаюсь, которое хочет, чтобы любое стороннее приложение (мое приложение) отправляло finish
Команда при выходе. Что лучше сказать, чтобы отправить это finish
команда, когда мое приложение было уничтожено с kill -9
?
редактировать 1: убить -9 не может быть захвачено. Спасибо, ребята, что поправили меня.
редактировать 2: я думаю, что это был бы случай, когда один вызывает просто kill, который совпадает с Ctrl-C
6 ответов
Способ справиться с этим для чего-либо, кроме kill -9
было бы зарегистрировать отключение крюка. Если вы можете использовать (SIGTERM) kill -15
крюк отключения будет работать. (SIGINT) kill -2
Означает ли это, что программа корректно завершает работу и запускает обработчики отключения.
Регистрирует новый хук отключения виртуальной машины.
Виртуальная машина Java отключается в ответ на два вида событий:
- Программа завершается нормально, когда завершается последний поток, не являющийся демоном, или когда вызывается метод exit (эквивалентно System.exit), или
- Виртуальная машина прерывается в ответ на пользовательское прерывание, такое как ввод ^C, или общесистемное событие, такое как выход пользователя из системы или завершение работы системы.
Я попробовал следующую тестовую программу на OSX 10.6.3 и на kill -9
это НЕ запустило крюк отключения, как ожидалось. На kill -15
он запускает отключающий крюк каждый раз.
public class TestShutdownHook
{
public static void main(String[] args) throws InterruptedException
{
Runtime.getRuntime().addShutdownHook(new Thread()
{
@Override
public void run()
{
System.out.println("Shutdown hook ran!");
}
});
while (true)
{
Thread.sleep(1000);
}
}
}
Там нет никакого способа действительно изящно обращаться с kill -9
в любой программе.
В редких случаях виртуальная машина может прерваться, то есть прекратить работу, не выключившись полностью. Это происходит, когда виртуальная машина завершается извне, например, с помощью сигнала SIGKILL в Unix или вызова TerminateProcess в Microsoft Windows.
Единственный реальный вариант справиться с kill -9
чтобы другая программа-наблюдатель наблюдала за вашей основной программой или использовала скрипт-обертку. Вы можете сделать это с помощью сценария оболочки, который опрашивал ps
Команда ищет вашу программу в списке и действует соответственно, когда она исчезла.
#!/usr/bin/env bash
java TestShutdownHook
wait
# notify your other app that you quit
echo "TestShutdownHook quit"
Я ожидаю, что JVM изящно прерывает (thread.interrupt()
) все запущенные потоки, созданные приложением, хотя бы для сигналов SIGINT (kill -2)
а также SIGTERM (kill -15)
,
Таким образом, сигнал будет передан им, что позволит изящно отменить поток и завершить ресурс стандартными способами.
Но это не так (по крайней мере, в моей реализации JVM: Java(TM) SE Runtime Environment (build 1.8.0_25-b17), Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)
,
Как прокомментировали другие пользователи, использование перехватчиков завершения работы является обязательным.
Итак, как мне справиться с этим?
Ну, во-первых, меня это не волнует во всех программах, только в тех, где я хочу отслеживать отмены пользователей и неожиданные результаты. Например, представьте, что ваша Java-программа - это процесс, управляемый другим. Вы можете хотеть различить, было ли это прекращено изящно (SIGTERM
из процесса диспетчера) или произошло отключение (для автоматического перезапуска задания при запуске).
Исходя из этого, я всегда периодически информирую мои давно работающие потоки о прерванном статусе и InterruptedException
если они прерваны. Это позволяет завершать выполнение способом, контролируемым разработчиком (также приводя к тому же результату, что и стандартные операции блокировки). Затем на верхнем уровне стека потоков, InterruptedException
захвачена и проведена соответствующая очистка. Эти потоки закодированы, чтобы знать, как ответить на запрос прерывания. Высокая когезия.
Поэтому в этих случаях я добавляю ловушку завершения работы, которая, по моему мнению, должна делать JVM по умолчанию: прерывать все потоки, не являющиеся демонами, созданные моим приложением, которые все еще работают:
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.out.println("Interrupting threads");
Set<Thread> runningThreads = Thread.getAllStackTraces().keySet();
for (Thread th : runningThreads) {
if (th != Thread.currentThread()
&& !th.isDaemon()
&& th.getClass().getName().startsWith("org.brutusin")) {
System.out.println("Interrupting '" + th.getClass() + "' termination");
th.interrupt();
}
}
for (Thread th : runningThreads) {
try {
if (th != Thread.currentThread()
&& !th.isDaemon()
&& th.isInterrupted()) {
System.out.println("Waiting '" + th.getName() + "' termination");
th.join();
}
} catch (InterruptedException ex) {
System.out.println("Shutdown interrupted");
}
}
System.out.println("Shutdown finished");
}
});
Заполните тестовое приложение на github: https://github.com/idelvall/kill-test
Существуют способы обработки ваших собственных сигналов в определенных JVM - см., Например, эту статью о JSM HotSpot.
Используя Солнце внутреннее sun.misc.Signal.handle(Signal, SignalHandler)
Вызов метода вы также можете зарегистрировать обработчик сигнала, но, вероятно, не для таких сигналов, как INT
или же TERM
как они используются JVM.
Чтобы иметь возможность обрабатывать любой сигнал, вы должны были бы выпрыгнуть из JVM на территорию операционной системы.
Что я обычно делаю, чтобы (например) обнаружить ненормальное завершение, это запустить мою JVM внутри сценария Perl, но сценарий ожидает, пока JVM использует waitpid
системный вызов.
Затем мне сообщают всякий раз, когда JVM выходит из системы и почему она выходит, и я могу предпринять необходимые действия.
Ты можешь использовать Runtime.getRuntime().addShutdownHook(...)
, но вы не можете быть уверены, что он будет вызван в любом случае.
Ссылка https://aws.amazon.com/blogs/containers/graceful-shutdowns-with-ecs/
import sun.misc.Signal;
import sun.misc.SignalHandler;
public class ExampleSignalHandler {
public static void main(String... args) throws InterruptedException {
final long start = System.nanoTime();
Signal.handle(new Signal("TERM"), new SignalHandler() {
public void handle(Signal sig) {
System.out.format("\nProgram execution took %f seconds\n", (System.nanoTime() - start) / 1e9f);
System.exit(0);
}
});
int counter = 0;
while(true) {
System.out.println(counter++);
Thread.sleep(500);
}
}
}
Есть один способ отреагировать на kill -9: это отдельный процесс, который отслеживает процесс, который уничтожается, и очищает его, если это необходимо. Это, вероятно, потребует IPC и потребует много усилий, и вы все равно можете переопределить его, убив оба процесса одновременно. Я предполагаю, что это не будет стоить неприятностей в большинстве случаев.
Тот, кто убивает процесс с -9, теоретически должен знать, что он делает, и что он может оставить вещи в противоречивом состоянии.