1. Overview
It's typically easy to start-up a service. However, sometimes we need to have a plan for gracefully shutting one down.
In this tutorial, we're going to take a look at different ways a JVM application can terminate. Then, we'll use Java APIs to manage JVM shutdown hooks. Please refer to this article to learn more about shutting down the JVM in Java applications.
2. JVM Shutdown
The JVM can be shut down in two different ways:
- A controlled process
- An abrupt manner
A controlled process shuts down the JVM when either:
- The last non-daemon thread terminates. For example, when the main thread exits, the JVM starts its shutdown process
- Sending an interrupt signal from the OS. For instance, by pressing Ctrl + C or logging off the OS
- Calling System.exit() from Java code
While we all strive for graceful shutdowns, sometimes the JVM may shut down in an abrupt and unexpected manner. The JVM shuts down abruptly when:
- Sending a kill signal from the OS. For example, by issuing a kill -9 <jvm_pid>
- Calling Runtime.getRuntime().halt() from Java code
- The host OS dies unexpectedly, for example, in a power failure or OS panic
3. Shutdown Hooks
The JVM allows registering functions to run before it completes its shutdown. These functions are usually a good place for releasing resources or other similar house-keeping tasks. In JVM terminology, these functions are called shutdown hooks.
Shutdown hooks are basically initialized but unstarted threads. When the JVM begins its shutdown process, it will start all registered hooks in an unspecified order. After running all hooks, the JVM will halt.
3.1. Adding Hooks
In order to add a shutdown hook, we can use the Runtime.getRuntime().addShutdownHook() method:
Thread printingHook = new Thread(() -> System.out.println("In the middle of a shutdown")); Runtime.getRuntime().addShutdownHook(printingHook);
Here we simply print something to the standard output before JVM shuts down itself. If we shut down the JVM like following:
> System.exit(129); In the middle of a shutdown
Then we'll see that the hook actually prints the message to standard output.
The JVM is responsible for starting hook threads. Therefore, if the given hook has been already started, Java will throw an exception:
Thread longRunningHook = new Thread(() -> { try { Thread.sleep(300); } catch (InterruptedException ignored) {} }); longRunningHook.start(); assertThatThrownBy(() -> Runtime.getRuntime().addShutdownHook(longRunningHook)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Hook already running");
Obviously, we also can't register a hook multiple times:
Thread unfortunateHook = new Thread(() -> {}); Runtime.getRuntime().addShutdownHook(unfortunateHook); assertThatThrownBy(() -> Runtime.getRuntime().addShutdownHook(unfortunateHook)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Hook previously registered");
3.2. Removing Hooks
Java provides a twin remove method to remove a particular shutdown hook after registering it:
Thread willNotRun = new Thread(() -> System.out.println("Won't run!")); Runtime.getRuntime().addShutdownHook(willNotRun); assertThat(Runtime.getRuntime().removeShutdownHook(willNotRun)).isTrue();
The removeShutdownHook() method returns true when the shutdown hook is successfully removed.
3.3. Caveats
The JVM runs shutdown hooks only in case of normal terminations. So, when an external force kills the JVM process abruptly, the JVM won't get a chance to execute shutdown hooks. Additionally, halting the JVM from Java code will also have the same effect:
Thread haltedHook = new Thread(() -> System.out.println("Halted abruptly")); Runtime.getRuntime().addShutdownHook(haltedHook); Runtime.getRuntime().halt(129);
The halt method forcibly terminates the currently running JVM. Therefore, registered shutdown hooks won't get a chance to execute.
4. Conclusion
In this tutorial, we looked at different ways a JVM application can possibly terminate. Then, we used a few runtime APIs to register and de-register shutdown hooks.
As usual, the sample code is available over on GitHub.