Backend Development 12 min read

Ensuring Safe JVM Shutdown with Shutdown Hooks and Signal Handling

To prevent loss of asynchronous tasks and data corruption during application restarts, this article explains how to safely shut down the JVM using shutdown hooks, custom signal handling, and proper hook implementation, while highlighting risks of forced termination and best‑practice considerations.

Dada Group Technology
Dada Group Technology
Dada Group Technology
Ensuring Safe JVM Shutdown with Shutdown Hooks and Signal Handling

Background

User: All goods have arrived, why are there still items in the cart?\nProduct: Users report that after order submission the cart is not cleared.\nR&D: Investigating… after a restart the asynchronous tasks were lost.\nProduct: Is it acceptable to lose tasks on restart?\nR&D: …

In order‑heavy transaction flows, many operations such as updating recent addresses, sending notifications via MQ, and clearing carts are performed asynchronously to improve response time, but this introduces a risk: if the JVM restarts before those asynchronous tasks finish, they may be lost.

JVM Shutdown

First, understand the situations that cause a JVM to shut down. The diagram below (original image) shows normal shutdown, abnormal shutdown, and forced termination (e.g., kill -9, Runtime.halt(), power loss). Forced termination gives the JVM no chance to run any cleanup code, which can lead to data loss, file corruption, task loss, or duplicate updates.

For normal or abnormal shutdowns, the JVM invokes all registered shutdown hooks before exiting. Therefore, we recommend using System.exit(0) for graceful termination instead of forceful methods.

JVM Safe Exit

For Tomcat‑based web applications, you can register a custom hook with Runtime.addShutdownHook(Thread hook) . For worker‑type applications, a signal‑based mechanism is preferred.

Signal‑Based Process Notification

Signals are an OS‑level asynchronous communication mechanism. Linux provides several termination signals (e.g., SIGKILL, SIGTERM, SIGINT). Windows has its own set (e.g., SIGINT, SIGTERM, SIGBREAK).

Signal Name

Purpose

SIGKILL

Forcefully terminate the process

SIGTERM

Software termination signal

SIGTSTP

Stop process from terminal

SIGPROF

Timer for profiling

SIGUSR1

User‑defined signal 1

SIGUSR2

User‑defined signal 2

SIGINT

Interrupt process (Ctrl+C)

SIGQUIT

Generate core dump and terminate

Windows equivalents:

Signal Name

Purpose

SIGINT

Ctrl+C interrupt

SIGTERM

Software termination (kill)

SIGBREAK

Ctrl+Break interrupt

We choose SIGUSR2 for Linux and SIGINT for Windows as custom signals that do not interfere with normal operations.

Safe Exit Implementation

Step 1 – Initialize a Signal instance:

Signal sig = new Signal(getOSSignalType());

Step 2 – Determine the appropriate signal name based on the OS:

private String getOSSignalType(){
    return System.getProperties().getProperty("os.name")
        .toLowerCase().startsWith("win") ? "INT" : "USR2";
}

Step 3 – Register the handler with the JVM:

Signal.handle(sig, shutdownHandler);

Step 4 – Implement the handler that registers a shutdown‑hook thread:

public class ShutdownHandler implements SignalHandler {
    /**
     * Process the received signal
     * @param signal the signal
     */
    public void handle(Signal signal) {
        // logic to register hook
    }
}

Step 5 – Register the actual shutdown hook:

private void registerShutdownHook(){
    Thread t = new Thread(new ShutdownHook(), "ShutdownHook-Thread");
    Runtime.getRuntime().addShutdownHook(t);
}

Step 6 – Trigger JVM exit after handling the signal:

Runtime.getRuntime().exit(0);

Step 7 – Define the work performed by the hook (e.g., cleanup, waiting for pending tasks):

class ShutdownHook implements Runnable{
    @Override
    public void run() {
        System.out.println("ShutdownHook execute start...");
        try {
            TimeUnit.SECONDS.sleep(10); // simulate cleanup work
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("ShutdownHook execute end...");
    }
}

A timeout (e.g., 30 seconds) can be added; if cleanup does not finish, the stop script may fall back to kill -9 .

Precautions When Using Shutdown Hooks

Hooks run concurrently; their execution order is not guaranteed. Consolidate related cleanup steps into a single hook to avoid race conditions or deadlocks.

Keep hook execution short; avoid heavy computation or blocking I/O that would delay JVM termination.

During OS shutdown, the hook may be abruptly terminated if the system forces the process to exit.

Do not register or deregister hooks from within a hook; doing so throws IllegalStateException .

Avoid calling System.exit() inside a hook (it can block shutdown); you may use Runtime.halt() if necessary.

Uncaught exceptions in a hook are handled by the thread’s default exception handler and do not affect other hooks or the JVM exit.

Conclusion

By employing shutdown hooks and custom signal handling, applications can ensure that asynchronous operations complete and resources are cleaned up before the JVM terminates, thereby avoiding data loss, file corruption, and other issues caused by forced termination.

For more articles from Xindada Technology, follow their public account.

backendJavaJVMSignal HandlingShutdown Hooks
Dada Group Technology
Written by

Dada Group Technology

Sharing insights and experiences from Dada Group's R&D department on product refinement and technology advancement, connecting with fellow geeks to exchange ideas and grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.