Как изящно обрабатывать сигнал SIGKILL в Java - PullRequest
103 голосов
/ 30 марта 2010

Как вы справляетесь с очисткой, когда программа получает сигнал уничтожения?

Например, есть приложение, к которому я подключаюсь, которое хочет, чтобы любое стороннее приложение (мое приложение) отправляло команду finish при выходе из системы. Что лучше всего сказать, чтобы отправить эту команду finish, когда мое приложение было уничтожено с kill -9?

edit 1: kill -9 не может быть захвачен. Спасибо, ребята, что поправили меня.

edit 2: Полагаю, этот случай будет, когда тот, кто вызывает просто kill, совпадает с ctrl-c

Ответы [ 5 ]

119 голосов
/ 30 марта 2010

невозможно для любой программы на любом языке обрабатывать SIGKILL. Это так, что всегда можно завершить программу, даже если она содержит ошибки или является вредоносной. Но SIGKILL - не единственный способ завершить программу. Другой заключается в использовании SIGTERM. Программы могут обрабатывать этот сигнал. Программа должна обрабатывать сигнал, выполняя контролируемое, но быстрое отключение. Когда компьютер выключается, последний этап процесса выключения отправляет каждому оставшемуся процессу SIGTERM, дает этим процессам отсрочку на несколько секунд, а затем отправляет им SIGKILL.

Способ справиться с этим для чего-либо другого , чем kill -9, заключается в регистрации shutdown hook. Если вы можете использовать ( SIGTERM ) kill -15, то отключение будет работать. ( SIGINT ) kill -2 DOES приводит к тому, что программа корректно завершает работу и запускает обработчики отключения.

Регистрирует новый хук отключения виртуальной машины.

Виртуальная машина Java отключается в ответ на два вида событий:

  • Программа завершается нормально, когда завершается последний поток, не являющийся демоном, или когда вызывается метод выхода (эквивалентно System.exit), или
  • Виртуальная машина завершает работу в ответ на пользовательское прерывание, такое как ввод ^ C, или общесистемное событие, такое как выход пользователя из системы или завершение работы системы.

Я попробовал следующую тестовую программу на OSX 10.6.3, а на kill -9 она NOT запустила хук отключения, как и ожидалось. На kill -15 it DOES каждый раз запускается отключающий крюк.

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 Окна.

Единственный реальный вариант обработки kill -9 - это заставить другую программу-наблюдатель наблюдать за вашей основной программой или использовать скрипт-обертку. Вы можете сделать это с помощью сценария оболочки, который опрашивает команду ps, ища вашу программу в списке и действует соответственно, когда она исчезает.

#!/usr/bin/env bash

java TestShutdownHook
wait
# notify your other app that you quit
echo "TestShutdownHook quit"
13 голосов
/ 30 марта 2010

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *} * * * * * * * * * * * * * * * * * *.

Используя внутренний вызов метода sun.misc.Signal.handle(Signal, SignalHandler) Sun, вы также можете зарегистрировать обработчик сигналов, но, вероятно, не для таких сигналов, как INT или TERM, поскольку они используются JVM.

Чтобы иметь возможность обрабатывать любой сигнал , вы должны выпрыгнуть из JVM на территорию операционной системы.

Что я обычно делаю, чтобы (например) обнаружить ненормальное завершение, так это запустить мою JVM внутри скрипта Perl, но скрипт должен ждать JVM с помощью системного вызова waitpid.

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

8 голосов
/ 18 ноября 2015

Я ожидаю, что 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

6 голосов
/ 30 марта 2010

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

0 голосов
/ 08 августа 2017

Существует один способ реагировать на уничтожение -9: это иметь отдельный процесс, который отслеживает уничтожаемый процесс и при необходимости очищает его. Это, вероятно, потребует IPC и потребует много усилий, и вы все равно можете переопределить его, убив оба процесса одновременно. Я предполагаю, что в большинстве случаев это не будет стоить неприятностей.

Кто бы ни убил процесс с -9, теоретически должен знать, что он делает, и что он может оставить вещи в противоречивом состоянии.

...