Как изящно обработать сигнал SIGKILL в Java
Как вы обрабатываете очистку, когда программа получает сигнал завершения?
Например, есть приложение, к которому я подключаюсь, которое хочет, чтобы любое стороннее приложение (мое приложение) отправляло finish
команду при выходе из системы. Что лучше всего сказать для отправки этой finish
команды, когда мое приложение было уничтожено с помощью kill -9
?
правка 1: kill -9 не может быть захвачен. Спасибо вам, ребята, за то, что поправили меня.
правка 2: я предполагаю, что в этом случае будет вызван вызов just kill, который совпадает с ctrl-c
Переведено автоматически
Ответ 1
Это невозможно для любой программы на любом языке обработать SIGKILL. Это позволяет всегда завершить работу программы, даже если она глючная или вредоносная. Но SIGKILL - не единственное средство завершения программы. Другой способ заключается в использовании SIGTERM. Программы могут обрабатывать этот сигнал. Программа должна обработать сигнал, выполнив контролируемое, но быстрое завершение работы. Когда компьютер выключается, заключительный этап процесса завершения работы отправляет каждому оставшемуся процессу SIGTERM, дает этим процессам отсрочку на несколько секунд, затем отправляет им SIGKILL.
Способ справиться с этим для чего угодно, иного, чем kill -9
было бы зарегистрировать перехват shutdown. Если вы можете использовать (SIGTERM) kill -15
, то перехват завершения работы сработает. (SIGINT) kill -2
ДЕЙСТВИТЕЛЬНО приводит к корректному завершению программы и запуску перехватов завершения работы.
Регистрирует новый перехват завершения работы виртуальной машины.
Виртуальная машина Java завершает работу в ответ на события двух типов:
- Программа завершается обычным образом, когда завершается последний поток, не связанный с демоном, или когда вызывается метод exit (эквивалентно System.exit), или
- Виртуальная машина завершает работу в ответ на прерывание пользователя, такое как ввод ^ C, или общесистемное событие, такое как выход пользователя из системы или завершение работы системы.
Я попробовал следующую тестовую программу на OSX 10.6.3, и на kill -9
она НЕ запустила перехват завершения работы, как ожидалось. На kill -15
it каждый раз запускает перехват завершения работы.
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"
Ответ 2
Я бы ожидал, что 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
Ответ 3
Существуют существуют способы обработки ваших собственных сигналов в определенных JVM - см., например, Эту статью о JVM HotSpot.
Используя внутренний sun.misc.Signal.handle(Signal, SignalHandler)
вызов метода Sun, вы также можете зарегистрировать обработчик сигналов, но, вероятно, не для сигналов типа INT
или TERM
, поскольку они используются JVM.
Чтобы иметь возможность обрабатывать любой сигнал, вам пришлось бы перейти из JVM на территорию операционной системы.
Что я обычно делаю, чтобы (например) обнаружить аномальное завершение, так это запускаю свою JVM внутри Perl-скрипта, но заставляю скрипт ждать JVM с помощью waitpid
системного вызова.
Затем мне сообщают, когда JVM завершает работу, и почему она завершилась, и я могу предпринять необходимые действия.
Ответ 4
Ссылка 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);
}
}
}