3 min read

The JVM Execution Lifecycle: From Bootstrap to Shutdown Hooks

Every time you type java -jar application.jar in a terminal or launch a microservice inside a Kubernetes container, a new Java Virtual Machine process is initialized, executed, and eventually terminated. Understanding the JVM execution lifecycle—from initial process creation and native VM thread bootstrapping to shutdown hook execution and signal handling—is essential for building resilient systems.

In this guide, we trace the step-by-step startup process of HotSpot JVM instances, explore background system threads, analyze graceful vs. abrupt termination (SIGTERM vs. SIGKILL), and write robust shutdown hooks.


1. The Step-by-Step JVM Bootstrap Lifecycle

The lifecycle of a JVM instance is managed through C/C++ native invocations executed by the native launcher binary (java executable).


sequenceDiagram
    participant OS as OS Launcher (java)
    participant CVM as C++ VM Engine (JNI_CreateJavaVM)
    participant Boot as Bootstrap ClassLoader
    participant Threads as System Thread Pool
    participant Main as Application Main Thread

    OS->>CVM: Parse JVM arguments (-Xms, -Xmx, -XX:+UseG1GC)
    CVM->>Boot: Load java.lang.Object & System Classes
    CVM->>Threads: Spawn GC Threads, Compiler Threads, Signal Handler
    CVM->>Main: Load Target Class & Invoke main(String[] args)
    Main->>Main: Execute Application Business Logic
    Main->>CVM: Exit main() or System.exit(0)
    CVM->>OS: Run Shutdown Hooks & Terminate Process

Phase 1: Native Launcher Invocation

When the OS executes java, the launcher parses command-line arguments (allocating heap bounds -Xms/-Xmx and selecting garbage collector policies). The launcher loads the shared native library (libjvm.so on Linux or jvm.dll on Windows) and calls the C function JNI_CreateJavaVM().

Phase 2: System Class Loading & Primitive Initialization

The Bootstrap ClassLoader initializes in Metaspace and loads core Java system classes: java.lang.Object, java.lang.Class, java.lang.String, and java.lang.System. Primitive data types and core system properties are bound into environment memory.

Phase 3: Background System Thread Spawning

Before application bytecode executes, the JVM spawns dedicated background operating system threads:


flowchart TD
    subgraph VMThreads["HotSpot Native System Threads"]
        VMThread["VM Thread\n(Executes STW Safepoints)"]
        GCThreads["GC Threads\n(Parallel/Concurrent Heap Sweeps)"]
        CompilerThreads["JIT Compiler Threads\n(Background C1/C2 Native Compiles)"]
        SignalThread["Signal Dispatcher Thread\n(Catches OS SIGTERM / SIGINT)"]
    end
  • VM Thread: Executes JVM operations requiring Stop-The-World (STW) safepoints.
  • GC Threads: Executes parallel/concurrent garbage collection sweeps.
  • Compiler Threads: Background JIT compilation threads compiling hot bytecode methods into native code.
  • Signal Dispatcher Thread: Catches OS signals (e.g., SIGTERM, SIGINT) and delegates them to internal handlers.

Phase 4: Main Thread Invocation

The JVM creates the main application thread, resolves the specified target class via the Application ClassLoader, locates the entry point method signature public static void main(String[] args), and begins bytecode interpretation.


2. The JVM Shutdown Lifecycle & Signal Handling

A JVM instance initiates process shutdown under two scenarios:

  1. Normal Exit: All non-daemon user threads complete execution, or code explicitly calls System.exit(code) or Runtime.getRuntime().exit(code).
  2. External OS Termination Signal: The operating system sends a SIGINT (Ctrl+C) or SIGTERM signal to the process ID.

flowchart TD
    ShutdownTrigger["Shutdown Triggered\n(System.exit() or SIGTERM)"] --> Phase1["Phase 1: Run Shutdown Hooks\n(Registered Runtime Hooks Run Concurrently)"]
    Phase1 --> Phase2["Phase 2: Run Finalizers\n(If runFinalizersOnExit is enabled)"]
    Phase2 --> Phase3["Phase 3: Stop JVM System Threads\n(JNI_DestroyJavaVM)"]
    Phase3 --> Terminated["Process Exit\n(Return Exit Code to OS Kernel)"]

Graceful Shutdown vs. Abrupt Termination

  • SIGTERM (Signal 15): Allows the JVM to execute registered shutdown hooks, close database connection pools, flush log buffers, and finish inflight HTTP requests gracefully. Kubernetes sends SIGTERM when terminating pods.
  • SIGKILL (Signal 9): Immediately terminates the OS process at the kernel level. The JVM receives zero warning—no shutdown hooks are executed, and memory buffers are terminated instantly.

3. Writing Production-Grade Shutdown Hooks

Java allows developers to register shutdown hook threads using Runtime.getRuntime().addShutdownHook(). Shutdown hooks execute concurrently when the JVM begins termination.

public class GracefulShutdownApp {

    public static void main(String[] args) {
        // Register JVM Shutdown Hook
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("[Shutdown Hook] Initiating graceful resource cleanup...");
            try {
                // Flush database buffers and close network sockets
                closeDatabaseConnections();
                System.out.println("[Shutdown Hook] Resource cleanup complete!");
            } catch (Exception e) {
                System.err.println("[Shutdown Hook] Cleanup failed: " + e.getMessage());
            }
        }, "shutdown-hook-thread"));

        System.out.println("Application started and running...");
    }

    private static void closeDatabaseConnections() throws InterruptedException {
        Thread.sleep(1000); // Simulate closing connection pool resources
    }
}

Best Practices for Shutdown Hooks:

  • Keep Execution Fast: Shutdown hooks must complete quickly (typically within container timeout periods like 30 seconds).
  • Avoid Thread Locks & Inter-Dependencies: Shutdown hooks run concurrently in non-deterministic order. Avoid lock dependencies across multiple hooks to prevent deadlocks during process exit.
  • Do Not Call System.exit() inside a Shutdown Hook: Calling System.exit() inside a shutdown hook will cause the JVM to block indefinitely.

Related Guides:
← PreviousJDK vs JRE vs JVM: Internal DifferencesNext →JVM Memory Layout: Heap, Stack & Metaspace