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~