Dry goods | detailed principle of shutdown hook

Introduction to shutdown hook

In java programs, it is easy to add a hook, shutdown hook, at the end of the process. The following code is usually added when the program starts

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

With Shutdown hook, we can

  • Do some remedial work at the end of the process, such as releasing occupied resources, saving program state, etc
  • Provide a means for elegant (smooth) release to remove traffic before the program is closed

Many java middleware or frameworks use the ability of shutdown hook, such as dubbo, spring, etc.

In spring, a shutdown hook will be registered when the application context is load ed.
The shutdown hook will destroy the bean and issue ContextClosedEvent before the process exits.
Under the spring framework, dubbo listens to contextclosedevevent and calls dubboBootstrap.stop() to clean up the scene and publish dubbo gracefully. By default, the event mechanism of spring is synchronous, so it can wait for all listeners to complete the execution when publishing events.

Shutdown hook principle

Data structure and execution order of shutdown hook

  • When we add a shutdown hook, we will call ApplicationShutdownHooks.add(hook) to add a hook to the static variable private static identityhashmap < thread, thread > hooks under the ApplicationShutdownHooks class. The hook itself is a thread object
  • When the ApplicationShutdownHooks class is initialized, it will add hooks to the Shutdown hooks. The Shutdown hooks are system level shutdownhooks, and the system level shutdownhooks are composed of an array, and only 10 can be added
  • The system level shutdown hook calls the run method of the thread class, so the system level shutdown hook is executed synchronously and orderly
private static void runHooks() {
    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;
                throw td;
            }
        }
    }
}
  • The add method of the system level shutdown hook is visible to the package, that is, we cannot call it directly
  • ApplicationShutdownHooks is located at subscript 1, and application level hooks call the start method of thread class during execution, so application level shutdownhooks are executed asynchronously, but they will not exit until all hooks are executed.
static void runHooks() {
    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) {
            }
        }
    }
}

Summarize with a picture as follows:

Shutdown hook trigger point

From the runHooks of Shutdown, we can get the following call path

Focus on Shutdown.exit   and   Shutdown.shutdown

Shutdown.exit

Follow up the caller of Shutdown.exit and find that   Runtime.exit   and   Terminator.setup

  • Runtime.exit   It is the interface that actively ends the process in the code
  • Terminator.setup   cover   initializeSystemClass   Call, which is triggered when the first thread is initialized. After triggering, a signal monitoring function is registered to capture the signal sent by kill, and call Shutdown.exit to end the process

This covers the scenarios of actively ending the process and being killed by kill in the code.

There is no need to introduce the active end process. Let's talk about signal acquisition here. In java, we can write the following code to capture kill signals. We only need to implement the SignalHandler interface and handle method, and register the corresponding signals to be monitored at the program entrance. 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() {
                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);
    }
}

It should be noted that generally speaking, after capturing signals and doing some personalized processing, we need to actively call System.exit, otherwise the process will not exit. At this time, we can only use kill -9 to forcibly kill the process.

Moreover, each signal capture is in different threads, so the execution between them is asynchronous.

Shutdown.shutdown

This method can be seen in the comments

/* 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. */

This method will be called by JNI's DestroyJavaVM method at the end of the last non daemon thread (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. The sign for the JVM to judge whether to end is whether there are still user threads working.
Called when the last user thread ends   Shutdown.shutdown. This is a unique "right" of virtual machine languages such as JVM. If golang is compiled into executable binary files, ShutdownHook will not be executed when all user threads end.

For example, when the java process exits normally, it does not actively end the process in the code or kill, just like this

public static void main(String[] args) {

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

After the main thread runs, I'm shutdown hook can also be printed out. On the contrary, golang can't do this (if you can do it, you can tell me in a private letter that I'm a novice to golang)

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

We can see that the shutdown hook of java actually covers a very comprehensive area. There is only one area that cannot be covered, that is, when we use kill -9 when killing a process, the program cannot capture and process, and the process is killed directly, so the shutdown hook cannot be executed.

summary

To sum up, we draw some conclusions

  • When rewriting the capture signal, you should pay attention to actively exiting the process, otherwise the process may never exit, and the execution of the capture signal is asynchronous
  • The user level shutdown hook is bound to the system level shutdown hook, and the user level is asynchronous execution, the system level is synchronous sequential execution, and the user level is the second in the system level execution sequence
  • ShutdownHook covers a wide range. Whether you manually call the interface to exit the process, capture a signal to exit the process, or the user thread exits after execution, ShutdownHook will be executed. The only thing that will not be executed is kill -9

If this article is helpful to you, please leave you for three consecutive days~

 

Tags: Java Back-end

Posted on Fri, 22 Oct 2021 07:22:23 -0400 by Thoughtless