This is the second day of my participation in Gwen Challenge
This article is participating in “Java Theme Month – Java Development in Action”, see the activity link for details
I am Chen PI, an ITer of Internet Coding. I search “Chen PI’s JavaLib” on wechat and read the latest articles as soon as possible, reply to [information], and then I can get the technical materials, electronic books, interview materials of first-line big factories and excellent resume templates carefully arranged by me.
background
If you want to do some extra processing when a Java process exits, both normal and abnormal, such as resource cleaning, object destruction, persisting in-memory data to disk, waiting for the thread pool to finish processing all tasks, and so on. Especially in the case of abnormal process hang, if some important state is not preserved in time, or the task of the thread pool is not finished, it may cause serious problems. So what to do?
The 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
In essence, the shutdown hook is a simple thread that has been initialized but not started. When the virtual machine starts to shut down, it calls all registered hooks, which execute concurrently and in an indeterminate order.
During the shutdown process, you can continue to register new hooks or unregister existing hooks. However, it is possible to throw an IllegalStateException. Methods to register and unregister hooks are defined as follows:
public void addShutdownHook(Thread hook) {
/ / to omit
}
public void removeShutdownHook(Thread hook) {
/ / to omit
}
Copy the code
Close the hook being called scenario
Closing hooks can be called in the following scenarios:
- Program exit normally
- The program calls system.exit () to exit
- The terminal uses Ctrl+C to interrupt the program
- The program throws exceptions that cause the program to exit, such as OOM, array out of bounds, etc
- System events, such as users logging out or shutting down the system
- Run the Kill pid command to Kill the process. Caution The Kill -9 pid command does not trigger the execution hook
Verify the normal exit of the program
package com.chenpi;
public class ShutdownHookDemo {
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Execute hook method...")));
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Program starts...");
Thread.sleep(2000);
System.out.println("Program is about to exit..."); }}Copy the code
The results
The program starts... Program about to exit... Execute hook method... Process finished withexit code 0
Copy the code
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 method...")));
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Program starts...");
Thread.sleep(2000);
System.exit(-1);
System.out.println("Program is about to exit..."); }}Copy the code
The results
The program starts... Execute hook method... Process finished withexit code -1
Copy the code
Verify that the terminal interrupts the program using Ctrl+C, runs the program in a command line window, and then interrupts using Ctrl+C
package com.chenpi;
public class ShutdownHookDemo {
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Execute hook method...")));
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Program starts...");
Thread.sleep(2000);
System.out.println("Program is about to exit..."); }}Copy the code
The results
D:\IdeaProjects\java-demo\java ShutdownHookDemo
The program starts...
Execute hook method...
Copy the code
Demo throwing an exception causes the program to exit unexpectedly
package com.chenpi;
public class ShutdownHookDemo {
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Execute hook method...")));
}
public static void main(String[] args) {
System.out.println("Program starts...");
int a = 0;
System.out.println(10 / a);
System.out.println("Program is about to exit..."); }}Copy the code
The results
The program starts... Execute hook method... Exceptionin thread "main" java.lang.ArithmeticException: / by zero
at com.chenpi.ShutdownHookDemo.main(ShutdownHookDemo.java:12)
Process finished with exit code 1
Copy the code
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 a virtual machine, but note that these hooks are executed concurrently and in an indeterminate order.
package com.chenpi;
public class ShutdownHookDemo {
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Execute hook method A...")));
Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Execute hook method B...")));
Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("Execute hook method C...")));
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Program starts...");
Thread.sleep(2000);
System.out.println("Program is about to exit..."); }}Copy the code
The results
The program starts... Program about to exit... Execute hook method B... Execute hook method C... Execute hook method A...Copy the code
The hook method registered with the virtual machine should be completed as soon as possible. Try not to take a long time, such as I/O may be blocked operations, deadlock, etc., so that the program can not be closed for a short period of time, or even permanently closed. We can also introduce a timeout mechanism to force the hook to exit and allow the program to end normally.
package com.chenpi;
public class ShutdownHookDemo {
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// Simulate a long operation
try {
Thread.sleep(1000000);
} catch(InterruptedException e) { e.printStackTrace(); }})); }public static void main(String[] args) throws InterruptedException {
System.out.println("Program starts...");
Thread.sleep(2000);
System.out.println("Program is about to exit..."); }}Copy the code
The above hooks take a long time to execute and ultimately cause the application to wait a long time before closing.
If the JVM has already called a hook closing process, it is not allowed to register new hooks or unregister registered hooks, otherwise an IllegalStateException will be reported. From source code analysis, when the JVM calls hooks, the ApplicationShutdownHooks#runHooks() method, it takes all hooks out of the hooks variable hooks and sets it to NULL.
// Call the execution hook
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) {
try {
hook.join();
} catch (InterruptedException x) { }
}
}
Copy the code
In the methods for registering and unregistering hooks, the hooks variable is first determined to be null, and an exception is thrown if it is.
// Register the hook
static synchronized 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);
}
// Unregister the hook
static synchronized boolean remove(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook == null)
throw new NullPointerException();
returnhooks.remove(hook) ! =null;
}
Copy the code
So let’s do that
package com.chenpi;
public class ShutdownHookDemo {
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Execute hook method...");
Runtime.getRuntime().addShutdownHook(new Thread(
() -> System.out.println(An IllegalStateException is reported when a hook is registered while the JVM is calling it.)));
// An IllegalStateException is reported when the HOOK is unregistered while the JVM is calling it
Runtime.getRuntime().removeShutdownHook(Thread.currentThread());
}));
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Program starts...");
Thread.sleep(2000);
System.out.println("Program is about to exit..."); }}Copy the code
The results
The program starts... Program about to exit... Execute hook method... Exceptionin 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$staticThe $1(ShutdownHookDemo.java:8)
at java.lang.Thread.run(Thread.java:748)
Copy the code
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 method...")));
}
public static void main(String[] args) {
System.out.println("Program starts...");
System.out.println("Program is about to exit...");
Runtime.getRuntime().halt(0); }}Copy the code
The results
The program starts... Program about to exit... Process finished withexit code 0
Copy the code
If you want to terminate an executing hook method, you can only force the program out by calling runtime.geTruntime ().halt(). In Linux, you can run the kill -9 pid command to forcibly terminate the 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("Finish executing hook methods...");
}));
}
public static void main(String[] args) {
System.out.println("Program starts...");
System.out.println("Program is about to exit..."); }}Copy the code
The results
The program starts... Program about to exit... Start executing hook methods... Process finished withexit code -1
Copy the code
If your program uses Java Security Managers, using shutdown hooks requires a Security RuntimePermission(” shutdownHooks “), otherwise a SecurityException will result.
practice
For example, our program customizes a thread pool to receive and process tasks. If the program crashes suddenly and exits abnormally, all tasks in the thread pool may not be completed. If the program is not finished and exits directly, important problems such as data loss and service exceptions may occur. 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 {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Start executing hook methods...");
// Close the thread pool
executorService.shutdown();
try {
// Wait 60 seconds
System.out.println(executorService.awaitTermination(60, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Finish executing hook methods...");
}));
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Program starts...");
// Add 10 tasks to the 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..."); }}}Copy the code
Run the program in the command line window, interrupt the program with Ctrl+C after all 10 tasks have been submitted to the thread pool, and wait 60 seconds for all tasks to complete.
Shutdown Hook in Spring
How does the Shutdown Hook work in Spring? Through source code analysis, Springboot project startup will determine whether the value of registerShutdownHook is true, the default is true, if true to register the virtual machine to close the hook.
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(a) {
if (this.shutdownHook == null) {
// No shutdown hook registered yet.
this.shutdownHook = new Thread() {
@Override
public void run(a) {
synchronized (startupShutdownMonitor) {
// Hook methoddoClose(); }}};// The bottom layer still registers the hook this way
Runtime.getRuntime().addShutdownHook(this.shutdownHook); }}Copy the code
The doClose method does some virtual-close processing, such as destroying all singleton beans in the container, closing the BeanFactory, publishing the close event, and so on.
protected void doClose(a) {
// 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 {
// Publish a closure event for the Spring application context so that the listener responds before the application is closed
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 {
// Execute the lifecycleProcessor shutdown method
this.lifecycleProcessor.onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex); }}// Destroy all singleton beans in the container
destroyBeans();
/ / close the 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); }}Copy the code
As we know, we can define the bean and implement the DisposableBean interface, overriding the Destroy object destruction method. The destroy method is called in the Spring registered close hook. For example, we use the Spring framework’s ThreadPoolTaskExecutor thread pool class, which implements the DisposableBean interface, and overwrites the destroy method, so that the thread pool can be destroyed before the application exits. The source code is as follows:
@Override
public void destroy(a) {
shutdown();
}
/**
* Perform a shutdown on the underlying ExecutorService.
* @see java.util.concurrent.ExecutorService#shutdown()
* @see java.util.concurrent.ExecutorService#shutdownNow()
*/
public void shutdown(a) {
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); }}Copy the code