The JVM shutdown hook: an introduction to the Java programmer.

Marcio Endo
June 6, 2022

If you are developing an application in Java there is a probability you will be using at least one of the following:

These kind of libraries must hold system resources for doing their job. When the application is terminated, those resources must be released. While the operating system will reclaim all of a process resources upon termination you might want to do it in a orderly manner (if possible). For example:

So when can these types of task be executed? Can they be executed at all?

They sound like good use-cases for a JVM shutdown hook.

In this blog post we will go through an introduction to the JVM shutdown hook facility.

What is the JVM shutdown hook facility?

Simply put the JVM provides a facility for running tasks during its shutdown operation.

You do so by adding a Thread instance via the Runtime.addShutdownHook method:

var runtime = Runtime.getRuntime();

runtime.addShutdownHook(
  new Thread() {
    @Override
    public void run() {
      // this will be run during the JVM shutdown
    }
  }
);

The Runtime class also provides a removeShutdownHook method to remove a previously registered hook.

Hooks can be registered at any time. For most of my use cases the registering occurs during the application initialization.

When does the JVM shuts down?

So you have defined a shutdown hook. When does the JVM run it?

Before we go into that, let's first understand what triggers the JVM to shutdown.

Let's start with the following example:

public class Example {
  public static void main(String[] args) {
    say("Hello");

    say("Bye");
  }

  private static void say(String what) {
    var ct = Thread.currentThread();

    var name = ct.getName();

    System.out.println("%4s: %s".formatted(name, what));
  }
}

It is a "Hello world" type of application. It additionally prints the name of the thread calling the say method.

Running the example prints:

$ java src/main/java/iter1/Example.java
main: Hello
main: Bye

$ echo $?
0

As expected, the program prints "Hello" and "Bye" and then exits. Its exit code was 0 so we can assume the JVM terminated normally.

Also from the program output we see that the main method was run by the main thread. It ended after the main thread returned from the main method. And since the JVM has no more instructions to execute it shuts down.

Starting an user thread

So if a program stops executing after it returns from the main method how can we make it not do it?

Put in other words, how can we make the program outlive the main thread?

Let's refactor our initial example:

public class Example {
  public static void main(String[] args) {
    say("Hello");

    var cntr = new Thread(
      Example::countdown, "cntr"
    );

    cntr.start();

    say("Bye");
  };

  private static void countdown() {
    say("Hello");

    say("3 seconds countdown!");

    for (int i = 3; i > 0; i--) {
      say(Integer.toString(i));

      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        say("Got interrupted...");

        return;
      }
    }

    say("Bye");
  }

  private static void say(String what) {
    var ct = Thread.currentThread();

    var name = ct.getName();

    System.out.println("%4s: %s".formatted(name, what));
  }
}

After saying "Hello" we start a thread named cntr that does a 3 seconds countdown. The countdown thread uses Thread.sleep and, in case of an InterruptedException, it just stops counting.

Running this example prints:

main: Hello
main: Bye
cntr: Hello
cntr: 3 seconds countdown!
cntr: 3
cntr: 2
cntr: 1
cntr: Bye

Unlike our first example, the program did not stop after it returned from the main method. Instead, it stopped after the cntr thread returned from the countdown method.

Could it be that the JVM shuts down when all threads exit? Well, close to it.

The Runtime.addShutdownHook method javadocs lists the events that triggers the JVM to shut down. One of the events could be written as:

That is what happened in both of our examples so far.

Starting a daemon thread

In the last example we saw that the program exits when the last non-daemon thread exits.

So what happens if we start a daemon thread instead?

Let's modify our last example.

Before starting the cntr thread we mark it as a daemon thread:

var cntr = new Thread(
  Example::countdown, "cntr"
);

cntr.setDaemon(true);

cntr.start();

Additionally, after starting the cntr thread and before saying "Bye" in the main method, we have the main thread sleep a little so it gives a chance for the cntr thread do a little work:

cntr.start();

try {
  Thread.sleep(100);
} catch (InterruptedException e) {
  say("Got interrupted...");

  return;
}

say("Bye");

Running this example gives the following output:

main: Hello
cntr: Hello
cntr: 3 seconds countdown!
main: Bye

The program output ends as soon as the main thread exits. Since the main thread is the only non-daemon thread, the JVM shuts down.

What is worth noting from the output it that the cntr thread was forcefully terminated. By forcefully terminated I mean it was not interrupted: there is no "Got interrupted..." message from the cntr thread.

One could argue that, perhaps, the cntr thread never reached the Thread.sleep instruction.

Maybe... let's keep investigating.

Terminating by typing <CTRL-C>

The Runtime.addShutdownHook method javadocs lists another event that triggers the JVM to shut down. It could be written as:

So let's go back to our second example. It is the one where we start a non-daemon countdown thread.

This time, we request the application to stop execution by typing <CTRL-C> in the console. We do it after the program outputs the number 2:

$ java src/main/java/iter2/Example.java
main: Hello
main: Bye
cntr: Hello
cntr: 3 seconds countdown!
cntr: 3
cntr: 2
^C

After <CTRL-C> is pressed, the JVM shuts down.

In a similar way as to our previous example, it seems the cntr was forcefully terminated once again. Remember that, in this case, cntr is a non-daemon thread.

Once again, the thread was not interrupted. It was simply killed.

We will keep this in mind but, for now, the important thing is that we have discussed two answers for our running question "when does the JVM shuts down?":

Example: flushing an async file logger

In the introduction I mentioned a possible use-case for shutdown hooks: flushing the last pending messages of a file logger (or appender in more usual logging terms).

Suppose our file logger does its logging in an asynchronous manner. In other words, the actual writing of the log messages occurs in a distinct thread of the one invoking the logger itself.

If during the JVM shutdown this thread is simply killed as opposed to being interrupted, when can we flush the messages?

One possible solution is to use a shutdown hook.

To keep things simple we will reuse our running example. Assume our countdown thread is our async file logger:

public class Example {
  private static Thread cntr;

  public static void main(String[] args) {
    say("Hello");

    // 1
    cntr = new Thread(
      Example::countdown, "cntr"
    );

    var rt = Runtime.getRuntime();

    // 2
    rt.addShutdownHook(
      new Thread(
        Example::hook, "hook"
      )
    );

    cntr.start();

    say("Bye");
  };

  private static void countdown() {
    say("Hello");

    say("3 seconds countdown!");

    for (int i = 3; i > 0; i--) {
      say(Integer.toString(i));

      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        say("Got interrupted. Flushing messages...");

        return;
      }
    }

    say("Bye");
  }

  private static void hook() {
    say("Hello");

    // 3
    if (cntr != null && cntr.isAlive()) {
      say("I will interrupt cntr");

      cntr.interrupt();

      while (cntr.isAlive()) {
        say("cntr is alive");

        Thread.onSpinWait();
      }
    }

    say("Bye");
  }

  private static void say(String what) {
    var ct = Thread.currentThread();

    var name = ct.getName();

    System.out.println("%4s: %s".formatted(name, what));
  }
}

The three numbered code blocks are described below:

  1. First, we keep a reference of the cntr thread. In the spirit of keeping things simple, we store the reference in a static field named cntr

  2. Next, we add a shutdown hook. The thread is named hook and its Runnable instance is given by the Example::hook method reference

  3. Finally, in the shutdown hook, we interrupt the cntr thread if it is possible to do so (if the reference is not null and if the thread is alive). We busy-wait while the cntr is still alive printing "cntr is alive" in the process.

Let's run the example without interruption. It prints:

$ java src/main/java/iter4/Example.java
main: Hello
main: Bye
cntr: Hello
cntr: 3 seconds countdown!
cntr: 3
cntr: 2
cntr: 1
cntr: Bye
hook: Hello
hook: Bye

The shutdown hook runs after both the main and the cntr thread exit. Except for the "Hello" and "Bye" greetings, our hook does not do any work.

Let's run the example again. This time, we request the program to stop execution by pressing <CTRL-C> in the console. We do it after the program outputs the number 2:

$ java src/main/java/iter4/Example.java
main: Hello
main: Bye
cntr: Hello
cntr: 3 seconds countdown!
cntr: 3
cntr: 2
^C
hook: Hello
hook: I will interrupt cntr
hook: cntr is alive
hook: cntr is alive
hook: cntr is alive
cntr: Got interrupted. Cleaning up...
hook: cntr is alive
hook: Bye

This time the shutdown hook interrupts the cntr thread. In response to being interrupted, the cntr thread manages to do its cleanup.

In our file logger example we would flush any pending messages.

Why Thread and not Runnable?

One question I personally had was: why does the Runtime.addShutdownHook method declares a Thread parameter and not, say, a Runnable instead?

This seems to be a common question since it was added to this FAQ: Design of the Shutdown Hooks API. I could only find this document for the Java 8 release. But I assume it is still up-to-date.

The gist of it is that it simplifies both the specification and the implementation.

It makes sense. I do not know the internals of the JVM but I assume the Thread class is a core component. By this I am assuming:

So, during the shutdown process, all the JVM has to do is start the specified threads. I am sure is more complicated than that (as thing usually are) but I am happy with the answer given by the FAQ.

Conclusion

In the blog post we discussed a few topics on the JVM shutdown hook facility. The intention was to give an introduction to the Java programmer.

I believe many Java programmers do not have to deal with shutdown hooks directly. Libraries or frameworks will abstract hooks away. I am not sure though. In any case, I believe it is nice to know how things work.

There are more topics on this subject that were not discussed. For example:

We might revisit these topics in a future blog post.

As usual, examples in this post can be found in this GitHub repository.