Hello, everyone, I am peach, background private letter [information], you can get my carefully organized technical data, electronic books, the first line of large factory interview information and excellent resume template.

background

If you want to do both normal and abnormal exits when a Java process exits, do some extra processing, such as resource cleanup, object destruction, persistence of memory data to disk, waiting for the thread pool to finish processing all the tasks, and so on. In particular, if the process fails unexpectedly, it can cause serious problems if some important state is not retained in time, or if the thread pool tasks are not processed. So what should I do?

Shutdown Hook in Java provides a better solution. . We can through the Java Runtime. AddShutdownHook (Thread hook) method to the JVM registration closed hook, before the JVM exit will automatically call execute hook method, do some end operation, so that the process smooth graceful exit, guarantees the integrity of the business.

Shutdown the hooks to introduce

Shutdown Hook is simply a thread that has been initialized but has not been started. When the virtual machine begins to shut down, it will call all registered hooks that are executed concurrently and in an indomitable order.

While the virtual machine is closed, you can continue to register new hooks, or undo hooks that have already been registered. However, it is possible to throw an IllegalStateException. The methods for registering and unregistering hooks are defined as follows:

Public void removeHutdownHook (Thread Hook) {public void removeHutdownHook (Thread Hook) {

Close the scene where the hook is invoked

Close the hook can be invoked in several scenarios:

  1. Program exits normally
  2. The program calls System.exit() to exit
  3. The terminal uses CTRL +C to interrupt the program
  4. The program throws an exception that causes the program to exit, such as OOM, array out of bounds, etc
  5. System events, such as a user logging out or shutting down the system
  6. Kill the process with the Kill pid command. Note that forcing Kill with the Kill -9 pid command does not trigger hook execution
    • *

Verify that the program exits normally

package com.chenpi; public class ShutdownHookDemo { static { Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println(" Execute hook methods...") ))); } public static void main(String[] args) throws InterruptedException {System.out.println() throws InterruptedException {System.out.println(); ); Thread.sleep(2000); Println (" The program is about to exit...") ); }}

The results

The program starts... The program is about to exit... Execute the hook method... Process finished with exit code 0

Verify that the program calls System.exit() to exit

package com.chenpi; public class ShutdownHookDemo { static { Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println(" Execute hook methods...") ))); } public static void main(String[] args) throws InterruptedException {System.out.println() throws InterruptedException {System.out.println(); ); Thread.sleep(2000); System.exit(-1); Println (" The program is about to exit...") ); }}

The results

The program starts... Execute the hook method... Process finished with exit code -1

Verify that the terminal uses Ctrl+C to interrupt the program, run the program in a command-line window, and then use Ctrl+C to interrupt

package com.chenpi; public class ShutdownHookDemo { static { Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println(" Execute hook methods...") ))); } public static void main(String[] args) throws InterruptedException {System.out.println() throws InterruptedException {System.out.println(); ); Thread.sleep(2000); Println (" The program is about to exit...") ); }}

The results

D:\IdeaProjects\ Java-Demo \ Java ShutdownHookDemo Execute the hook method...

Demonstrates that throwing an exception causes a program to exit with an exception

package com.chenpi; public class ShutdownHookDemo { static { Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println(" Execute hook methods...") ))); } public static void main(String[] args) {System.out.println(String[] args) {System.out.println(String[] args); ); int a = 0; System.out.println(10 / a); Println (" The program is about to exit...") ); }}

The results

The program starts... Execute the hook method... Exception in thread "main" java.lang.ArithmeticException: / by zero at com.chenpi.ShutdownHookDemo.main(ShutdownHookDemo.java:12) Process finished with exit code 1

If the system is shut down, or the process is killed using the Kill pid command, you can verify it yourself.

Matters needing attention

You can register multiple closing hooks with the virtual machine, but note that these hook executions are concurrent and the order of execution is uncertain.

package com.chenpi; public class ShutdownHookDemo { static { Runtime.getRuntime().addShutdownHook(new Thread(() -> Println (" Execute hook method A...") ))); Runtime.getRuntime(). AddShutdownHook (new Thread(() -> System.out.println())) ))); Runtime.getRuntime(). AddShutdownHook (new Thread(() -> System.out.println())) ))); } public static void main(String[] args) throws InterruptedException {System.out.println() throws InterruptedException {System.out.println(); ); Thread.sleep(2000); Println (" The program is about to exit...") ); }}

The results

The program starts... The program is about to exit... Execute hook method B... Execute hook method C... Execute hook method A...

Hook methods registered with the virtual machine need to be executed as soon as possible, and try not to perform long-running operations, such as I/O operations that may be blocked, deadlocks, etc., so that the program cannot be closed for a short time, or even for a long time. We can also introduce a timeout mechanism to force the hook to exit and let the program end normally.

package com.chenpi; Public class ShutdownDemo {static {run.getRuntime (). AddShutdownHook (new Thread(() ->) {public class ShutdownDemo {static {run.getRuntime (). Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); }})); } public static void main(String[] args) throws InterruptedException {System.out.println() throws InterruptedException {System.out.println(); ); Thread.sleep(2000); Println (" The program is about to exit...") ); }}

The above hooks take a long time to execute and eventually cause the program to wait a long time before it can be closed.


If the JVM has already called the process of performing a close hook, it is not allowed to register new hooks and unregister registered hooks, otherwise an IllegalStateException is reported. Through source code analysis, when the JVM calls hooks, the applicationShutdownHooks #runHooks() method is called, it removes all hooks from the variable hooks and sets this variable to null.

Static void runHooks() {Collection<Thread> threads; synchronized(ApplicationShutdownHooks.class) { threads = hooks.keySet(); hooks = null; } for (Thread hook : threads) { hook.start(); } for (Thread hook : threads) { try { hook.join(); } catch (InterruptedException x) { } } }

In the methods that register and unhook, the hooks variable is first checked to see if it is null, and if so, an exception is thrown.

// Synchronized static void add(Thread hook) {if(hooks == null) throw new IllegalStateException("Shutdown in progress"); if (hook.isAlive()) throw new IllegalArgumentException("Hook already running"); if (hooks.containsKey(hook)) throw new IllegalArgumentException("Hook previously registered"); hooks.put(hook, hook); } // Unhook static synchronized Boolean remove(Thread hook) {if(hooks == null) throw new IllegalStateException("Shutdown in progress"); if (hook == null) throw new NullPointerException(); return hooks.remove(hook) ! = null; }

So let’s do that

package com.chenpi; public class ShutdownHookDemo { static { Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println(" Execute hook methods...") ); Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println(" register a new hook during the JVM's call to the hook, Complains an IllegalStateException "))); IllegalStateException Runtime.getRuntime().removeHutdownHook (Thread.currentThread()); })); } public static void main(String[] args) throws InterruptedException {System.out.println() throws InterruptedException {System.out.println(); ); Thread.sleep(2000); Println (" The program is about to exit...") ); }}

The results

The program starts... The program is about to exit... Execute the hook method... Exception in thread "Thread-0" java.lang.IllegalStateException: Shutdown in progress at java.lang.ApplicationShutdownHooks.add(ApplicationShutdownHooks.java:66) at java.lang.Runtime.addShutdownHook(Runtime.java:211) at com.chenpi.ShutdownHookDemo.lambda$static$1(ShutdownHookDemo.java:8) at java.lang.Thread.run(Thread.java:748)

If the Runtime.getRuntime().halt() method is called to stop the JVM, the virtual machine will not call the hook.

package com.chenpi; public class ShutdownHookDemo { static { Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println(" Execute hook methods...") ))); } public static void main(String[] args) {System.out.println(String[] args) {System.out.println(String[] args); ); Println (" The program is about to exit...") ); Runtime.getRuntime().halt(0); }}

The results

The program starts... The program is about to exit... Process finished with exit code 0

If you want to terminate a hook method in execution, you can only force the program to exit by calling Runtime.getRuntime().halt(). Using the kill -9 pid command in a Linux environment can also force termination and exit.

package com.chenpi; public class ShutdownHookDemo { static { Runtime.getRuntime().addShutdownHook(new Thread(() -> { System.out.println(" Start executing hook methods...") ); Runtime.getRuntime().halt(-1); System.out.println(" End hook method execution...") ); })); } public static void main(String[] args) {System.out.println(String[] args) {System.out.println(String[] args); ); Println (" The program is about to exit...") ); }}

The results

The program starts... The program is about to exit... Start executing hook methods... Process finished with exit code -1

If the program uses Java Security Managers, using Shutdown Hook requires the Security permission RuntimePermission(” shutdownHooks “), which otherwise results in a SecurityException.

practice

For example, our program has a custom thread pool to receive and process tasks. If the program crashes suddenly and exits unexpectedly, then all tasks of the thread pool may not be processed yet. If the program is not processed, it may directly exit, which may lead to data loss, business exception and other important problems. That’s where the hook comes in.

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; Public class ShutdownHookDemo {/ / thread pool private static ExecutorService ExecutorService = Executors. NewFixedThreadPool (3);  Static {run.getRuntime (). AddShutdownHook (new Thread(() -> {System.out.println()); ); // Shutdown the thread pool executorService.shutdown(); Try {/ / wait for 60 SECONDS System. Out. Println (executorService. AwaitTermination (60, TimeUnit. SECONDS)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(" End hook method execution...") ); })); } public static void main(String[] args) throws InterruptedException {System.out.println() throws InterruptedException {System.out.println(); ); // Add 10 tasks to thread pool for (int I = 0; i < 10; i++) { Thread.sleep(1000); final int finalI = i; executorService.execute(() -> { try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Task " + finalI + " execute..." ); }); System.out.println("Task " + finalI + " is in thread pool..." ); }}}

Run the program in the command line window, use Ctrl+C to interrupt the program after all 10 tasks have been committed to the thread pool and before all tasks have been processed. Finally, before the virtual machine closes, call the close hook, close the thread pool, and wait 60 seconds for all tasks to complete.

Shutdown Hook usage in Spring

How Shutdown Hook is used in Spring. When SpringBoot project is started, it determines if RegisterShutdownHook is true. The default value is true. If it is true, it registers the closing hook with the virtual machine.

private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } } @Override public void registerShutdownHook() { if (this.shutdownHook == null) { // No shutdown hook registered yet. this.shutdownHook = new Thread() {@Override public void run() {synchronized (StartupShutdownMonitor) {// Synchronize (); }}}; GetRuntime ().addShutdownHook(this.shutdownHook); }}

In doClose, the hook closing method, some virtual machine closing pre-processing is done, such as destroying all the singleton beans in the container, closing the BeanFactory, publishing a closing event, and so on.

protected void doClose() { // Check whether an actual close attempt is necessary... if (this.active.get() && this.closed.compareAndSet(false, true)) { if (logger.isDebugEnabled()) { logger.debug("Closing " + this); } LiveBeansView.unregisterApplicationContext(this); Try {// PublishEvent (new ContextCloseDevent (this)) {// PublishEvent (new ContextCloseDevent (this)); } catch (Throwable ex) { logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); } // Stop all Lifecycle beans, to avoid delays during individual destruction. if (this.lifecycleProcessor ! = null) {try {/ / execution lifecycleProcessor method of closing this. LifecycleProcessor. OnClose (); } catch (Throwable ex) { logger.warn("Exception thrown from LifecycleProcessor on context close", ex); }} destroyBeans();} // Destroy all singleton beans in the container; // Close BeanFactory CloseBeanFactory (); // Let subclasses do some final clean-up if they wish... onClose(); // Reset local application listeners to pre-refresh state. if (this.earlyApplicationListeners ! = null) { this.applicationListeners.clear(); this.applicationListeners.addAll(this.earlyApplicationListeners); } // Switch to inactive. this.active.set(false); }}

As we know, we can define the bean and implement the DisposableBean interface, overwriting the Destroy object destruction method. The destroy method is called inside a Spring-registered close hook. For example, we use the Spring Framework ThreadPoolTaskExecutor thread pool class, which implements the DisposableBean interface and overwrites the destroy method to perform thread pool destruction prior to program exit. The source is as follows:

@Override public void destroy() { shutdown(); } /** * Perform a shutdown on the underlying ExecutorService. * @see java.util.concurrent.ExecutorService#shutdown() * @see java.util.concurrent.ExecutorService#shutdownNow() */ public void shutdown() { if (logger.isInfoEnabled()) { logger.info("Shutting down ExecutorService" + (this.beanName ! = null ? " '" + this.beanName + "'" : "")); } if (this.executor ! = null) { if (this.waitForTasksToCompleteOnShutdown) { this.executor.shutdown(); } else { for (Runnable remainingTask : this.executor.shutdownNow()) { cancelRemainingTask(remainingTask); } } awaitTerminationIfNecessary(this.executor); }}