Como lidar perfeitamente com o sinal SIGKILL em Java

113

Como você lida com a limpeza quando o programa recebe um sinal de eliminação?

Por exemplo, há um aplicativo ao qual me conecto que deseja que qualquer aplicativo de terceiros (meu aplicativo) envie um finishcomando ao fazer logout. Qual é a melhor coisa a dizer para enviar esse finishcomando quando meu aplicativo for destruído com um kill -9?

editar 1: kill -9 não pode ser capturado. Obrigado pessoal por me corrigir.

edição 2: Eu acho que este caso seria quando aquele chama apenas kill, que é o mesmo que ctrl-c

Begui
fonte
44
kill -9significa para mim: "Vá embora, processo sujo, vá embora!", quando o processo cessará. Imediatamente.
ZoogieZork
11
Na maioria dos * nixes que eu conheço, kill -9 não pode ser interceptado e manipulado de maneira elegante por qualquer programa, não importa em que idioma foi escrito.
Presidente James K. Polk
2
@Begui: além do que os outros comentaram e responderam, SE o seu SO Un x não matar instantaneamente * E RECUPERAR TODOS OS RECURSOS usados ​​pelo programa que está sendo eliminado -9 ' , bem ... O SO está quebrado.
SintaxeT3rr0r
1
Sobre o comando kill -9, a página de manual diz mais precisamente: "9 KILL (non-catchable, non-ignorable kill)". SIGKILL um sinal que é controlado pelo sistema operacional, não pelo aplicativo.
user1338062
4
Simplesmente killnão é o mesmo que Ctrl-C, pois killsem especificar qual sinal enviar enviará SIGTERM, enquanto Ctrl-C enviará SIGINT.
alesguzik

Respostas:

136

É impossível para qualquer programa, em qualquer linguagem, lidar com um SIGKILL. Assim, sempre é possível encerrar um programa, mesmo que ele tenha bugs ou seja malicioso. Mas o SIGKILL não é o único meio de encerrar um programa. A outra é usar um SIGTERM. Os programas podem lidar com esse sinal. O programa deve controlar o sinal fazendo um desligamento controlado, mas rápido. Quando um computador é desligado, o estágio final do processo de desligamento envia a cada processo restante um SIGTERM, dá a esses processos alguns segundos de graça e, em seguida, envia um SIGKILL.

A maneira de lidar com isso para qualquer coisa outra que kill -9seria registrar um encerramento gancho. Se você puder usar ( SIGTERM ), kill -15o gancho de desligamento funcionará. ( SIGINT ) kill -2 FAZ com que o programa saia normalmente e execute os ganchos de desligamento.

Registra um novo gancho de desligamento da máquina virtual.

A máquina virtual Java é desligada em resposta a dois tipos de eventos:

  • O programa sai normalmente, quando o último encadeamento não daemon sai ou quando o método de saída (equivalentemente, System.exit) é chamado, ou
  • A máquina virtual é encerrada em resposta a uma interrupção do usuário, como digitar ^ C, ou um evento de todo o sistema, como logoff do usuário ou desligamento do sistema.

Tentei o seguinte programa de teste no OSX 10.6.3 e kill -9nele NÃO executei o gancho de desligamento, como esperado. Em uma kill -15ele FAZ executar o gancho de desligamento de cada vez.

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);
        }
    }
}

Não há nenhuma maneira de lidar com um kill -9em nenhum programa.

Em raras circunstâncias, a máquina virtual pode abortar, ou seja, interromper a execução sem desligar corretamente. Isso ocorre quando a máquina virtual é encerrada externamente, por exemplo, com o sinal SIGKILL no Unix ou a chamada TerminateProcess no Microsoft Windows.

A única opção real para lidar com um kill -9é fazer com que outro programa inspetor observe se seu programa principal vai embora ou use um script wrapper. Você poderia fazer isso com um script de shell que pesquisasse o pscomando à procura de seu programa na lista e agiria de acordo quando ele desaparecesse.

#!/usr/bin/env bash

java TestShutdownHook
wait
# notify your other app that you quit
echo "TestShutdownHook quit"
Raedwald
fonte
12

Não são maneiras de lidar com os seus próprios sinais em determinadas JVMs - ver este artigo sobre o HotSpot JVM por exemplo.

Usando a sun.misc.Signal.handle(Signal, SignalHandler)chamada de método interna da Sun , você também pode registrar um manipulador de sinal, mas provavelmente não para sinais como INTou TERMcomo são usados ​​pela JVM.

Para ser capaz de lidar com qualquer sinal, você teria que pular da JVM para o território do sistema operacional.

O que geralmente faço para (por exemplo) detectar o encerramento anormal é iniciar minha JVM dentro de um script Perl, mas fazer com que o script aguarde a JVM usando a waitpidchamada do sistema.

Eu sou então informado sempre que a JVM é encerrada e por que ela foi encerrada, e posso tomar as medidas necessárias.

Asgeir S. Nilsen
fonte
3
Observe que você pode capturar INTe TERMcom sun.misc.Signal, mas não pode controlar QUITporque a JVM reserva para depuração, nem KILLporque o sistema operacional irá encerrar a JVM imediatamente. A tentativa de lidar com qualquer um deles gerará um IllegalArgumentException.
dimo414 de
12

Eu esperaria que a JVM interrompa ( thread.interrupt()) normalmente todos os threads em execução criados pelo aplicativo, pelo menos para sinais SIGINT (kill -2)e SIGTERM (kill -15).

Dessa forma, o sinal será encaminhado a eles, permitindo um cancelamento de thread e finalização de recursos nas formas padrão .

Mas este não é o caso (pelo menos na minha implementação 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).

Como outros usuários comentaram, o uso de ganchos de desligamento parece obrigatório.

Então, como eu lidaria com isso?

Bem, em primeiro lugar, eu não me importo com isso em todos os programas, apenas naqueles em que desejo manter o controle de cancelamentos de usuários e finais inesperados. Por exemplo, imagine que seu programa java é um processo gerenciado por outro. Você pode querer diferenciar se ele foi encerrado normalmente (a SIGTERMpartir do processo do gerenciador) ou se ocorreu um encerramento (para reiniciar automaticamente o trabalho na inicialização).

Como base, eu sempre deixo meus threads de longa duração periodicamente cientes do status de interrupção e lanço um InterruptedExceptionse eles foram interrompidos. Isso permite a finalização da execução de forma controlada pelo desenvolvedor (também produzindo o mesmo resultado que as operações de bloqueio padrão). Então, no nível superior da pilha de encadeamentos, InterruptedExceptioné capturado e a limpeza apropriada é realizada. Esses threads são codificados para saber como responder a uma solicitação de interrupção. Design de alta coesão .

Portanto, nesses casos, adiciono um gancho de desligamento, que faz o que acho que a JVM deve fazer por padrão: interromper todos os encadeamentos não daemon criados por meu aplicativo que ainda estão em execução:

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");
    }
});

Aplicação de teste completa no github: https://github.com/idelvall/kill-test

idelvall
fonte
6

Você pode usar Runtime.getRuntime().addShutdownHook(...), mas não pode ter certeza de que será chamado em qualquer caso .

Lexicore
fonte
12
Mas no caso de kill -9 quase certamente não funcionará.
Presidente James K. Polk,
1

Há uma maneira de reagir a um kill -9: isso é ter um processo separado que monitora o processo que está sendo morto e o limpa depois, se necessário. Isso provavelmente envolveria IPC e seria um pouco trabalhoso, e você ainda pode substituí-lo eliminando os dois processos ao mesmo tempo. Suponho que não valerá a pena na maioria dos casos.

Quem mata um processo com -9 deve, teoricamente, saber o que está fazendo e que isso pode deixar as coisas em um estado inconsistente.

Arno Schäfer
fonte