CLOVER🍀

That was when it all began.

Runtime#addShutdownHook、sun.misc.SignalHandlerを使ってシグナルを扱う

これまでちゃんと使ったことがなかったRuntime#addShudownHookと、個人的にメモとして書き残しておきたいsun.misc.SignalHandlerについて、ちょっと書いておきます。

それぞれどんなものなのかですが、Runtime#addShutdownHookはJavaアプリケーションの終了時に実行させるThreadを登録することができるものです。これを使うことで、Javaアプリケーションの終了時に何らかの処理を実行することができます。まあ、いついかなる時も実行されるというわけではありませんが。

sun.misc.SignaHandlerは、sum.miscという推奨されないパッケージですが、Javaでシグナルを扱うことができる(シグナルハンドラを登録することができる)ようになります。

以下に、順に使っていってみましょう。

推奨は、Runtime#addShutdownHookかと思いますので、こちらから。

なお、実行環境はUbuntu Linux 14.04 LTSで、kill -lが以下のように表示されます。

$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

Runtime#addShutdownHook

Runtimeのインスタンスの、addShutdownHookにThreadを登録することで、Javaアプリケーションの終了時に登録したスレッドを実行させることができます。

今回は、シグナルを送って反応するかどうかを確認してみました。

Integrating Signal and Exception Handling
http://www.oracle.com/technetwork/java/javase/signals-139944.html

このドキュメントによると、SIGTERM、SIGINT、SIGHUPに対して有効と書かれていますが、実際その通りでした。

サンプルとして作成したコード。
ShutdownHookTest.java

import java.io.Console;

public class ShutdownHookTest {
    public static void main(String... args) {
        Runtime
            .getRuntime()
            .addShutdownHook(new Thread(() -> System.out.println("Run Shutdown Hook.")));
        
        Console console = System.console();
        console.readLine("Enter stop this program.\n");
    }
}

起動すると、このようにEnterを入力するまで終了を待機します。

$ java ShutdownHookTest
Enter stop this program.

このプロセスに対して、killコマンドでシグナルを送ってみます。

$ kill -HUP [PID]

すると、addShutdownHookで登録した処理が実行されて終了しました。

$ java ShutdownHookTest
Enter stop this program.
Run Shutdown Hook.

機能していますね。

繰り返しますが、反応したシグナルはSIGTERM、SIGINT、SIGHUPとなります。

sun.misc.SignalHandler

sun.miscという非推奨パッケージに属するクラスですが、このSignalHandlerおよびSignalを使うことで、シグナルをハンドリングすることができます。

使い方は、こんな感じです。

Signal.handle(new Signal("INT"),  new SignalHandler() {
    public void handle(Signal signal) {
        // シグナルを受け取って何か処理
    }
}

Signalに対して、SignalHandlerインターフェースを実装したクラスを登録します。

で、うちの環境で「kill -l」で表示されたシグナルに対して、できる限りSignalHandlerを登録してみました。

できあがったソースコードは、こちら。
SignalHandlerTest.java

import java.io.Console;
import java.util.Arrays;

import sun.misc.Signal;
import sun.misc.SignalHandler;

public class SignalHandlerTest {
    public static void main(String... args) {
        Signal[] signals = {
            new Signal("HUP"),
            new Signal("INT"),
            // new Signal("QUIT"),  // java.lang.IllegalArgumentException: Signal already used by VM or OS: SIGQUIT
            // new Signal("ILL"),  // java.lang.IllegalArgumentException: Signal already used by VM or OS: SIGILL
            new Signal("TRAP"),
            new Signal("ABRT"),
            new Signal("BUS"),
            // new Signal("FPE"),  // java.lang.IllegalArgumentException: Signal already used by VM or OS: SIGFPE
            // new Signal("KILL"),  // java.lang.IllegalArgumentException: Signal already used by VM or OS: SIGKILL
            // new Signal("USR1"),  // java.lang.IllegalArgumentException: Signal already used by VM or OS: SIGUSR1
            // new Signal("SEGV"),  // java.lang.IllegalArgumentException: Signal already used by VM or OS: SIGSEGV
            new Signal("USR2"),
            new Signal("PIPE"),
            new Signal("ALRM"),
            new Signal("TERM"),
            new Signal("STKFLT"),
            new Signal("CHLD"),
            new Signal("CONT"),
            // new Signal("STOP"),  // java.lang.IllegalArgumentException: Signal already used by VM or OS: SIGSTOP
            new Signal("TSTP"),
            new Signal("TTIN"),
            new Signal("TTOU"),
            new Signal("URG"),
            new Signal("XCPU"),
            new Signal("XFSZ"),
            new Signal("VTALRM"),
            new Signal("PROF"),
            new Signal("WINCH"),
            new Signal("IO"),
            new Signal("PWR"),
            new Signal("SYS")
            // ここから下は、Unknown扱い
            // Unknown signal: RTMAX
            // new Signal("RTMIN"),
            // new Signal("RTMIN+1"),
            // new Signal("RTMIN+2"),
            // new Signal("RTMIN+3"),
            // new Signal("RTMIN+4"),
            // new Signal("RTMIN+5"),
            // new Signal("RTMIN+6"),
            // new Signal("RTMIN+7"),
            // new Signal("RTMIN+8"),
            // new Signal("RTMIN+9"),
            // new Signal("RTMIN+10"),
            // new Signal("RTMIN+11"),
            // new Signal("RTMIN+12"),
            // new Signal("RTMIN+13"),
            // new Signal("RTMIN+14"),
            // new Signal("RTMIN+15"),
            // new Signal("RTMAX-14"),
            // new Signal("RTMAX-13"),
            // new Signal("RTMAX-12"),
            // new Signal("RTMAX-11"),
            // new Signal("RTMAX-10"),
            // new Signal("RTMAX-9"),
            // new Signal("RTMAX-8"),
            // new Signal("RTMAX-7"),
            // new Signal("RTMAX-6"),
            // new Signal("RTMAX-5"),
            // new Signal("RTMAX-4"),
            // new Signal("RTMAX-3"),
            // new Signal("RTMAX-2"),
            // new Signal("RTMAX-1"),
            // new Signal("RTMAX"),
        };

        SignalHandler handler = new MyHandler();

        Arrays
            .stream(signals)
            .forEach(s -> Signal.handle(s, handler));

        Console console = System.console();
        console.readLine("Enter stop this program.\n");
    }

    private static class MyHandler implements SignalHandler {
        @Override
        public void handle(Signal signal) {
            System.out.printf("Trap Signal name = [%s], number = [%d]%n", signal.getName(), signal.getNumber());
        }
    }
}

SignalHandlerを実装したクラスには、単にシグナルの名前と値を出力させるようにしました。

    private static class MyHandler implements SignalHandler {
        @Override
        public void handle(Signal signal) {
            System.out.printf("Trap Signal name = [%s], number = [%d]%n", signal.getName(), signal.getNumber());
        }
    }

コメントアウトしているのは、Java VMやOSによって予約されており、登録できなかったシグナルです。

つまり、sun.misc.SignalHandlerを使ってハンドリングできるのは、以下だということです。

シグナル
SIGHUP 1
SIGINT 2
SIGTRAP 5
SIGABRT 6
SIGBUS 7
SIGUSR2 12
SIGPIPE 13
SIGALRM 14
SIGTERM 15
SIGSTKFLT 16
SIGCHLD 17
SIGCONT 18
SIGTSTP 20
SIGTTIN 21
SIGTTOU 22
SIGURG 23
SIGXCPU 24
SIGXFSZ 25
SIGVTALRM 26
SIGPROF 27
SIGWINCH 28
SIGIO 29
SIGPWR 30
SIGSYS 31

さすがに、SIGKILLなどはムリだということですね。

では、作成したプログラムを起動してみます。

$ java SignalHandlerTest
Enter stop this program.

このプログラムに対して、登録したシグナルを送ってみます。

$ kill -HUP [PID]
$ kill -INT [PID]
$ kill -TRAP [PID]
$ kill -ABRT [PID]
$ kill -BUS [PID]
$ kill -USR2 [PID]
$ kill -PIPE [PID]
$ kill -ALRM [PID]
$ kill -TERM [PID]
$ kill -STKFLT [PID]
$ kill -CHLD [PID]
$ kill -CONT [PID]
$ kill -TSTP [PID]
$ kill -TTIN [PID]
$ kill -TTOU [PID]
$ kill -URG [PID]
$ kill -XCPU [PID]
$ kill -XFSZ [PID]
$ kill -VTALRM [PID]
$ kill -PROF [PID]
$ kill -WINCH [PID]
$ kill -IO [PID]
$ kill -PWR [PID]
$ kill -SYS [PID]

結果は、このような感じに。

$ java MySignalHandler 
Enter stop this program.
Trap Signal name = [HUP], number = [1]
Trap Signal name = [INT], number = [2]
Trap Signal name = [TRAP], number = [5]
Trap Signal name = [ABRT], number = [6]
Trap Signal name = [BUS], number = [7]
Trap Signal name = [USR2], number = [12]
Trap Signal name = [PIPE], number = [13]
Trap Signal name = [ALRM], number = [14]
Trap Signal name = [TERM], number = [15]
Trap Signal name = [STKFLT], number = [16]
Trap Signal name = [CHLD], number = [17]
Trap Signal name = [CONT], number = [18]
Trap Signal name = [TSTP], number = [20]
Trap Signal name = [TTIN], number = [21]
Trap Signal name = [TTOU], number = [22]
Trap Signal name = [URG], number = [23]
Trap Signal name = [XCPU], number = [24]
Trap Signal name = [XFSZ], number = [25]
Trap Signal name = [VTALRM], number = [26]
Trap Signal name = [PROF], number = [27]
Trap Signal name = [WINCH], number = [28]
Trap Signal name = [IO], number = [29]
Trap Signal name = [PWR], number = [30]
Trap Signal name = [SYS], number = [31]

ちゃんと、シグナルがハンドリングできていますね。

なお、このプログラムはCtr-cで終了しなくなりますので、ご注意を(笑)。

^CTrap Signal name = [INT], number = [2]

というわけで、だいたいの使い方はわかりました。

基本的にはRuntime#addShutdownHookを使うと思いますが、備忘録的に。

合わせて覚えたい

「-Xrs」オプションを付与してJavaアプリケーションを起動すると、シグナルのトラップの仕方がまた変わるようです。

詳しくは、このあたりを参考に。

Java SEツール・リファレンス(UNIX) - 5 アプリケーションの作成と構築 java
http://docs.oracle.com/javase/jp/8/docs/technotes/tools/unix/java.html#BGBCIEFC

Java SEツール・リファレンス(Windows) - 5 アプリケーションの作成と構築 java
http://docs.oracle.com/javase/jp/8/docs/technotes/tools/windows/java.html#BGBCIEFC

Javaアプリケーションをログオフ中も実行させ続けるには
http://www.atmarkit.co.jp/fwin2k/win2ktips/654javaxrs/javaxrs.html