ShutdownHook introduction

In Java programs, it’s easy to add a hook, called a ShutdownHook, when a process ends. The following code is usually added at startup time

Runtime.getRuntime().addShutdownHook(new Thread(){
    @Override
    public void run(a) {
        System.out.println("I'm shutdown hook..."); }});Copy the code

With ShutdownHook we can

  • Do some cleaning up at the end of the process, such as releasing occupied resources, saving program state, etc
  • Provides a means for elegant (smooth) publishing to remove traffic before the application is closed

ShutdownHook capabilities are used by many Java middleware or frameworks, such as Dubbo, Spring, and so on.

Spring registers a ShutdownHook when the application context is loaded. The ShutdownHook will destroy the bean before the process exits, emitting ContextClosedEvent and so on. Dubbo listens for ContextClosedEvent and calls dubboBootstrap. Stop () to clean up the field and release Dubbo. Spring’s event mechanism is synchronous by default. So you can wait for all listeners to complete at the Publish event.

ShutdownHook principle

Data structure and execution sequence of ShutdownHook

  • When we add a ShutdownHookApplicationShutdownHooks.add(hook)And went toApplicationShutdownHooksClassprivate static IdentityHashMap<Thread, Thread> hooksAdd a hook, which is itself a Thread object
  • ApplicationShutdownHooksWhen the class is initializedhooksAdded to theShutdownthehooks, andShutdownthehooksIs a system-level ShutdownHook, and system-level ShutdownHook is composed of an array, and only 10 shutdownhooks can be added
  • The system-level ShutdownHook calls the thread classrunMethod, so system-level shutdownhooks are executed synchronously and sequentially
private static void runHooks(a) {
    for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
        try {
            Runnable hook;
            synchronized (lock) {
                // acquire the lock to make sure the hook registered during
                // shutdown is visible here.
                currentRunningHook = i;
                hook = hooks[i];
            }
            if(hook ! =null) hook.run();
        } catch(Throwable t) {
            if (t instanceof ThreadDeath) {
                ThreadDeath td = (ThreadDeath)t;
                throwtd; }}}}Copy the code
  • System-level ShutdownHookaddThe method is package visible, meaning we can’t call it directly
  • ApplicationShutdownHooksThe subscript1, and app-level hooks are executed to call ThreadstartMethod, so the application-level ShutdownHook is executed asynchronously, but will not exit until all hooks have been executed.
static void runHooks(a) {
    Collection<Thread> threads;
    synchronized(ApplicationShutdownHooks.class) {
        threads = hooks.keySet();
        hooks = null;
    }

    for (Thread hook : threads) {
        hook.start();
    }
    for (Thread hook : threads) {
        while (true) {
            try {
                hook.join();
                break;
            } catch (InterruptedException ignored) {
            }
        }
    }
}
Copy the code

Here is a picture to summarize:

ShutdownHook trigger point

Following the runHooks of Shutdown, we have the following call path

The key to seeShutdown.exitShutdown.shutdown

Shutdown.exit

Follow up on the caller of shutdown. exit and find runtime. exit and Terminator.setup

  • Runtime.exitIs the interface in code that actively terminates a process
  • Terminator.setupinitializeSystemClassCall, which is triggered when the first thread is initialized, registers a signal monitoring function, and captureskillSignal, callShutdown.exitThe end of the process

This overrides the scenario in which the code actively terminates the process and the process is killed.

Active process termination need not be introduced, but signal capture is mentioned here. In Java, we can write the following code to catch the kill signal. We only need to implement the SignalHandler interface and the handle method, and the program entry register the corresponding signal to listen for. Of course, not every signal can be captured and processed.

public class SignalHandlerTest implements SignalHandler {

    public static void main(String[] args) {

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run(a) {
                System.out.println("I'm shutdown hook "); }}); SignalHandler sh =new SignalHandlerTest();
        Signal.handle(new Signal("HUP"), sh);
        Signal.handle(new Signal("INT"), sh);
        //Signal.handle(new Signal("QUIT"), sh); // The signal cannot be captured
        Signal.handle(new Signal("ABRT"), sh);
        //Signal.handle(new Signal("KILL"), sh); // The signal cannot be captured
        Signal.handle(new Signal("ALRM"), sh);
        Signal.handle(new Signal("TERM"), sh);

        while (true) {
            System.out.println("main running");
            try {
                Thread.sleep(2000L);
            } catch(InterruptedException e) { e.printStackTrace(); }}}@Override
    public void handle(Signal signal) {
        System.out.println("receive signal " + signal.getName() + "-" + signal.getNumber());
        System.exit(0); }}Copy the code

Note that in general, we need to call System.exit after we have captured the signal and done some personalization, otherwise the process will not exit, and we can only use kill -9 to force the process to be killed.

And each signal is captured in a different thread, so they are executed asynchronously.

Shutdown.shutdown

You can see the comments for this method

/* Invoked by the JNI DestroyJavaVM procedure when the last non-daemon
  * thread has finished.  Unlike the exit method, this method does not
  * actually halt the VM.
  */
Copy the code

This method will be called by JNI’s DestroyJavaVM method at the end of the last non-daemon thread.

There are two types of threads in Java, user threads and daemon threads. Daemon threads serve user threads, such as GC threads, and the FLAG that the JVM uses to determine whether a user thread is finished is whether there is still a user thread working. Shutdown. Shutdown is called when the last user thread ends. This is a “right” that is unique to virtual machine languages such as the JVM. In the case of an executable binary file such as Golang, ShutdownHook is not executed when all user threads end.

For example, when a Java process exits normally, there is no active process termination or kill in the code, as in this case

public static void main(String[] args) {

    Runtime.getRuntime().addShutdownHook(new Thread() {
        @Override
        public void run(a) {
            super.run();
            System.out.println("I'm shutdown hook "); }}); }Copy the code

When the main thread has finished running, it can also print “I’m shutdown hook”, whereas Golang can’t do this (if you can, please send me a private message telling me I’m a golang novice).

From the analysis of the above two calls, we can summarize the following conclusions:

We can see that Java’s ShutdownHook actually covers everything except one, which is when we kill a processkill -9Because the program cannot capture processing, the process was directly killed, so cannot executeShutdownHook.

conclusion

To sum up, we come to some conclusions

  • Rewriting the capture signal requires attention to actively exit the process, otherwise the process may never exit and the execution of the capture signal is asynchronous
  • The user-level ShutdownHook is bound to the system-level ShutdownHook, and the user-level is executed asynchronously, while the system-level is executed synchronously, and the user-level is second in the system-level execution order
  • ShutdownHook covers a wide range of areas. ShutdownHook is executed no matter manually calling the interface to exit the process, capturing the signal to exit the process, or exiting the user thread after execution. The only thing that is not executed is kill -9

About the author: focus on the back-end middleware development, the public number “catch bug master” author, follow me, give you simple technology dry goods