JVM Startup and Shutdown: Bootstrap, Initialization Hooks, and Shutdown Sequence
Understanding JVM lifecycle: bootstrap process, initialization hooks, shutdown sequence, and how to implement lifecycle callbacks for graceful shutdown.
JVM Startup and Shutdown: Bootstrap, Initialization Hooks, and Shutdown Sequence
The JVM goes through a well-defined lifecycle from startup to shutdown. Understanding this lifecycle helps you debug startup issues, implement graceful shutdown, and use initialization hooks for setup tasks that must run before your application starts.
This post covers the JVM bootstrap process, how classes are loaded during startup, initialization hooks that run on startup and shutdown, and the shutdown sequence itself.
Introduction
The JVM follows a rigorous lifecycle from the moment you invoke the java command until the process terminates. The bootstrap phase loads core JDK classes, initializes the garbage collector and JIT compiler, registers shutdown hooks, and eventually transfers control to your application main method. At the other end, the shutdown sequence runs registered hooks concurrently, finalizes remaining objects, and calls _exit() to hand control back to the operating system. Most developers never need to think about this lifecycle — it works correctly by default. But when startup problems surface in containerized environments, when graceful shutdown does not behave as expected, or when initialization hooks need to warm caches before the application begins, understanding the JVM lifecycle becomes essential.
For practitioners, JVM startup and shutdown matters in three scenarios. First, when debugging class loading order problems or circular dependency failures during bootstrap, where the sequence of events determines whether initialization succeeds or throws ExceptionInInitializerError. Second, when implementing graceful shutdown in services where containers send SIGTERM and expect the application to clean up and exit within a timeout — shutdown hooks that block indefinitely or get skipped entirely cause crashes, failed liveness probes, or resource leaks. Third, when working with initialization hooks for pre-warming caches or setting up profiling agents like JVMTI tools that must run before application code executes. Understanding when shutdown hooks run, what can cause them to be skipped, and how signal handling interacts with the lifecycle prevents production incidents that are difficult to debug after the fact.
This post walks through the bootstrap sequence step by step, explains how initialization and shutdown hooks work and what they can and cannot do, covers the five phases of the JVM shutdown sequence, describes signal handling on Unix-like systems, and provides production failure scenarios and implementation patterns for graceful shutdown. By the end, you will understand exactly what happens between java -jar and your application starting, and between calling System.exit() and the process actually terminating.
When NOT to Use
JVM startup and shutdown details matter when debugging startup failures, implementing graceful shutdown, or integrating with container orchestrators. A simple application that exits cleanly does not need you to understand bootstrap sequencing or hook registration. The JVM handles these correctly for most cases without intervention. Only dig into lifecycle internals when you hit class loading order problems, hook timing issues, or signal handling bugs.
Do not use initialization hooks for setup that belongs in static initializers or your main method. Hooks are JVM-internal and harder to debug. Do not use shutdown hooks for cleanup that must run every time, either. Shutdown hooks can be skipped entirely if the process gets killed with SIGKILL, the JVM crashes, or someone calls halt(). Put critical cleanup synchronously during normal operation, not asynchronously at shutdown where it may never execute.
Modifying the bootstrap classpath is rarely necessary and frequently breaks JVM assumptions. If you are adding classes there to work around classloader issues, fix the classloader problem directly. Shutdown hook timing is not guaranteed relative to specific application threads; do not use hooks for operations that need ordering guarantees without explicit synchronization. Runtime Data Areas and Execution Engine cover what gets initialized during bootstrap.
JVM Bootstrap Process
When you run java -jar myapp.jar, the JVM begins a complex bootstrap sequence before your main method executes.
Bootstrap Sequence
sequenceDiagram
participant OS as Operating System
participant JVM as JVM Process
participant Boot as Bootstrap Loader
participant Main as Main Thread
OS->>JVM: Create process, load JVM library
JVM->>Boot: Initialize Bootstrap ClassLoader
Boot->>Boot: Load core JDK classes
JVM->>JVM: Create runtime system (Thread, GC, etc.)
JVM->>JVM: Register shutdown hooks
JVM->>Main: Load application main class
Main->>Main: Execute main() method
Note over Main: Application runs
Main-->>JVM: main() returns or System.exit()
JVM->>JVM: Run shutdown hooks
JVM->>JVM: Finalize finalize() methods
JVM-->>OS: Process exits
Step-by-Step Bootstrap
-
Operating System creates the JVM process. This involves loading the JVM dynamic library (libjvm.so on Linux, jvm.dll on Windows) into memory and initializing native structures.
-
Bootstrap ClassLoader loads. The native bootstrap loader loads core Java classes from
rt.jar(Java 8 and earlier) or from the modules image (Java 9+). These includejava.lang.Object,java.lang.Class,java.lang.String, and all other fundamental classes. -
JVM runtime system initializes. This includes:
- Creating the GC system
- Initializing the interpreter
- Setting up the JIT compiler infrastructure
- Creating the main thread and thread group
- Initializing the class loader subsystem
-
Command line arguments are processed. The JVM parses
-X,-XX, and system properties. These affect GC algorithms, heap sizing, JIT behavior, and more. -
Shutdown hook registration. The JVM registers internal shutdown hooks (for flight recorder, etc.) and makes the application hooks registered via
Runtime.addShutdownHook()available. -
Application main class loads. The class specified on the command line is loaded. Static initializers run.
-
Main method executes. Control transfers to your application code.
Initialization Hooks
The JVM supports initialization hooks that run during startup. These are not the same as shutdown hooks, though the API is similar.
Using Initialization Hooks
Initialization hooks are threads that start during JVM initialization, before the application starts. They are useful for:
- Pre-warming caches
- Initializing native libraries
- Verifying external dependencies
- Setting up JVMTI (JVM Tool Interface) agents
public class InitializationHookExample {
static {
System.out.println("Static initializer runs during class loading");
}
public static void main(String[] args) {
System.out.println("Main method runs after initialization hooks");
}
}
Pre-Initialization with -XX:CompileCommand
For extremely early initialization, you can use the CompileCommand to force JIT compilation before first use:
java -XX:CompileCommand=option,MyClass::hotMethod,BiasedLocking -jar myapp.jar
This compiles MyClass.hotMethod() with biased locking enabled before the application starts.
Shutdown Hooks
Shutdown hooks are threads that the JVM runs during the shutdown sequence. They are guaranteed to run even if the JVM terminates abruptly.
When Shutdown Hooks Run
Shutdown hooks run when:
- All non-daemon threads have finished
System.exit()is called (from application code or another shutdown hook)- The JVM receives a termination signal (SIGTERM, Ctrl+C) and has not disabled shutdown hooks
- Runtime.halt() is called (forceful termination without running hooks, but shutdown hooks ARE run unless forced with _exit)
Implementing Shutdown Hooks
import java.util.concurrent.*
public class ShutdownHookExample {
private static final ExecutorService executor =
Executors.newFixedThreadPool(4);
public static void main(String[] args) {
// Register shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Shutdown hook running");
shutdownExecutor();
}, "shutdown-hook"));
// Application code
System.out.println("Application running");
}
private static void shutdownExecutor() {
executor.shutdown();
try {
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
Shutdown Hook Rules
-
Do not reference daemon threads. Daemon threads may terminate before the shutdown hook completes.
-
Do not depend on other application threads. By the time shutdown hooks run, all non-daemon threads have terminated.
-
Keep shutdown hooks fast. The JVM waits for all shutdown hooks to complete before exiting. Slow shutdown hooks delay termination.
-
Do not use System.exit() in a shutdown hook. This causes a deadlock as the JVM waits for the current shutdown hook to complete before starting the new exit.
-
Handle exceptions. Uncaught exceptions in shutdown hooks terminate the hook but do not stop other shutdown hooks.
-
Do not assume hooks run in a particular order. Multiple hooks may run concurrently.
Shutdown Sequence
The JVM shutdown sequence follows a defined order:
Phase 1: Shutdown Request
Shutdown is requested through one of:
- All non-daemon threads terminating normally
System.exit()call- Termination signal (SIGTERM, SIGINT, Ctrl+C)
Runtime.halt()call (immediate termination)
Phase 2: Running Shutdown Hooks
The JVM spawns new threads to run registered shutdown hooks. These hooks run concurrently and independently.
The JVM waits for all shutdown hooks to complete. There is no timeout; the JVM waits indefinitely unless:
- Runtime.halt() is called from a hook
- The JVM crashes
- The operating system terminates the process forcefully
Phase 3: Finalization
After all shutdown hooks complete, the JVM:
- Runs all uninvoked finalizers (if finalization is not disabled)
- Performs the last GC if needed
Note: As of Java 9+, Object.finalize() is deprecated and should not be used.
Phase 4: Process Exit
The JVM calls _exit() to terminate the process. At this point, all shutdown hooks have completed and finalization is done.
Signal Handling
On Unix-like systems, the JVM handles signals for:
- SIGTERM: Graceful shutdown request. Runs shutdown hooks.
- SIGINT: Interrupt from Ctrl+C. Runs shutdown hooks.
- SIGQUIT: Thread dump request (jstack). Does not trigger shutdown.
- SIGABRT: JVM crash. Generates hs_err crash log.
- SIGHUP: Often used for configuration reload. Not handled by JVM by default.
# Send SIGTERM for graceful shutdown
kill -TERM <pid>
# Send SIGINT (Ctrl+C equivalent)
kill -INT <pid>
# Request thread dump without killing
kill -QUIT <pid>
Custom Signal Handling
JVM TI (Tool Interface) agents can register for signal handling:
// JVM TI agent registration
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
jvmtiEnv *jvmti;
(*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_1_0);
// Set notification for breakpoint, single step, etc.
jvmtiError err = (*jvmti)->SetEventNotificationMode(
jvmti, JVMTI_ENABLE,
JVMTI_EVENT_BREAKPOINT, NULL);
return JNI_OK;
}
Production Failure Scenarios
| Scenario | Root Cause | Symptoms | Resolution |
|---|---|---|---|
| Shutdown hooks do not run | JVM killed with SIGKILL | Cleanup code skipped | Avoid SIGKILL, use SIGTERM |
| Deadlock during shutdown | Hook calls System.exit() | JVM hangs on shutdown | Remove System.exit() from hooks |
| Slow shutdown hooks | Blocking I/O in hook | Delayed shutdown | Make hooks non-blocking |
| Shutdown hook exception | Uncaught exception in hook | Hook terminates, others continue | Always catch exceptions in hooks |
| Finalizers run unexpectedly | Object became unreachable | Delayed cleanup, resource leaks | Avoid finalize(), use Cleaner |
| Signal handling blocked | Long shutdown hook or GC | SIGTERM ignored | Keep hooks fast, tune GC |
Trade-off Analysis
| Trade-off | Considerations |
|---|---|
| Fast Shutdown vs. Clean Shutdown | Forcing immediate termination risks data loss. Waiting for cleanup takes time. |
| Daemon vs. Non-Daemon Threads | Daemon threads die with the JVM, potentially leaving cleanup incomplete. Non-daemon threads keep the JVM alive. |
| Synchronous vs. Asynchronous Cleanup | Synchronous cleanup is simpler but can block. Asynchronous cleanup is non-blocking but more complex. |
| Signal Handling vs. Shutdown Hooks | Signals provide immediate response. Shutdown hooks run after the decision to terminate is made. |
Failure Scenarios Deep Dive
JVM Fails to Start Due to Class Loading Circular Dependency
A static initializer in class A triggers loading of class B, whose static initializer triggers loading of class C, which eventually triggers loading back to class A before A’s static initialization completes. The JVM detects this circularity and throws a StackOverflowError or ExceptionInInitializerError. This happens when static fields have complex initialization expressions that indirectly reference the same class. The fix is to break the circular dependency: move the initialization to a separate method called explicitly after construction, or refactor to remove the circular reference.
Shutdown Hook Never Runs Because All Non-Daemon Threads Never Terminate
If your application uses non-daemon threads that never terminate (for example, a thread pool with blocking tasks, or a long-running background task), the JVM never proceeds to the shutdown hook phase. The application just sits running even after main() returns. Ensure your application explicitly calls System.exit() or sets daemon threads appropriately if you rely on shutdown hooks for cleanup.
Flight Recorder Crash Dump Generated on JVM Crash but Process Exits Before You Can Read It
When the JVM crashes (SIGABRT), it generates an hs_err log and may start a Java Flight Recorder dump. If the disk fills up during the dump or the process is killed immediately after generating the crash log, the dump may be incomplete or truncated. Configure -XX:ErrorFile= to write the hs_err log to a partition with sufficient space, and ensure monitoring detects disk space issues before they cause dump truncation.
Implementation Patterns
// Graceful shutdown with proper resource cleanup
public class GracefulShutdown {
private volatile boolean shuttingDown = false;
public static void main(String[] args) {
GracefulShutdown app = new GracefulShutdown();
app.registerShutdownHook();
app.run();
}
private void registerShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
shuttingDown = true;
cleanup();
}));
}
private void run() {
while (!shuttingDown) {
// Application loop
processNextTask();
}
}
private void cleanup() {
System.out.println("Cleaning up resources");
// Close files, connections, etc.
}
private void processNextTask() {
// Task processing
}
}
// Distributed tracing context propagation during shutdown
public class TracingShutdown {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// Export tracing data before exit
Tracing.flush();
}));
}
}
// Health check endpoint for graceful load balancer removal
public class HealthCheckServer {
private volatile boolean healthy = true;
public void start() {
// Register shutdown hook to set unhealthy
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
healthy = false;
// Give load balancer time to remove from pool
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}));
// Start HTTP server on /health
}
public boolean isHealthy() {
return healthy;
}
}
Observability Checklist
- Monitor JVM uptime to detect unexpected restarts
- Log shutdown hook execution times to detect slow shutdowns
- Track graceful vs. forced shutdown ratio
- Monitor for SIGKILL (immediate termination) occurrences
- Ensure heap dumps on OOM are written before shutdown
- Verify shutdown hooks complete before process exit
- Track finalizer queue depth if using deprecated finalize()
- Monitor for native memory leaks that prevent clean shutdown
- Loguncaught exceptions in shutdown hooks
Security Notes
Shutdown hooks run with full JVM privileges. Malicious shutdown hooks can execute arbitrary code. Ensure shutdown hooks are registered only by trusted code.
External processes that send signals to the JVM should be authorized. Anyone who can send SIGTERM to the JVM process can trigger shutdown hooks.
Do not store sensitive data in shutdown hook threads without synchronization. By the time shutdown hooks run, other application threads have terminated.
Common Pitfalls / Anti-Patterns
Registering shutdown hooks from library code. Libraries should never register shutdown hooks unless absolutely necessary. Library shutdown hooks can conflict with application shutdown hooks and cause unpredictable behavior.
Assuming shutdown is always graceful. SIGKILL (kill -9) terminates the JVM immediately without running shutdown hooks. Service managers like systemd send SIGTERM first, but processes can be killed with SIGKILL if they do not terminate quickly enough.
Using shutdown hooks for critical cleanup. Shutdown hooks are not guaranteed to run in all cases (JVM crash, SIGKILL, Runtime.halt()). Critical cleanup should happen synchronously before critical operations, not asynchronously at shutdown.
Blocking in shutdown hooks. If a shutdown hook blocks, the JVM waits indefinitely. Avoid any blocking operations: network calls, file I/O beyond what’s absolutely necessary, or acquiring locks that other threads might hold.
Forgetting that daemon threads do not trigger shutdown hooks to wait. If your application relies on non-daemon threads to prevent premature shutdown, remember that shutdown hooks run after those threads terminate. If those threads are holding resources needed by hooks, you have a problem.
Quick Recap Checklist
- JVM bootstrap loads core classes, initializes GC and JIT, then runs main()
- Shutdown hooks run when the JVM receives termination signal or System.exit()
- All non-daemon threads must terminate before shutdown hooks run
- Shutdown hooks run concurrently and independently
- Do not call System.exit() from within a shutdown hook
- Keep shutdown hooks fast and handle all exceptions
- Runtime.halt() terminates immediately without running hooks
- SIGKILL (-9) cannot be intercepted and bypasses shutdown hooks
- Finalizers run after shutdown hooks complete
- Daemon threads may terminate during shutdown without cleanup
- Shutdown hooks are not guaranteed to run in all termination scenarios
Interview Questions
When shutdown is requested (via System.exit(), signal, or all non-daemon threads terminating), the JVM first determines if shutdown hooks are registered. If so, it spawns threads to run all registered shutdown hooks concurrently and waits for them to complete. After all shutdown hooks finish, the JVM runs any uninvoked finalizers (deprecated in Java 9+). Finally, the JVM calls _exit() to terminate the process. The key guarantee is that shutdown hooks are run even if the JVM terminates due to an error in the application code, but they are NOT run if the process is killed with SIGkill.
If a shutdown hook calls System.exit(), it causes a deadlock. The JVM has already started shutdown hook execution and is waiting for all hooks to complete before proceeding with the rest of the shutdown sequence. When System.exit() is called from a hook, the JVM attempts to start a new shutdown sequence, which cannot proceed because the current shutdown is still in progress. The JVM will hang indefinitely. The solution is to never call System.exit() from a shutdown hook. If you need to terminate from a hook, use Runtime.getRuntime().halt() instead.
Inside a shutdown hook, call executor.shutdown() to stop accepting new tasks. Then call executor.awaitTermination() with a timeout to wait for running tasks to complete. If the timeout expires, call executor.shutdownNow() to cancel running tasks and remove queued tasks from the queue. Always handle InterruptedException: if interrupted during awaitTermination, decide whether to continue waiting or cancel immediately. The shutdown hook should return (not block indefinitely) to allow the JVM to continue the shutdown sequence.
The JVM handles several signals: SIGTERM requests graceful shutdown, running shutdown hooks before terminating. SIGINT (Ctrl+C) also requests graceful shutdown. SIGQUIT requests a thread dump without terminating, useful for diagnostics. SIGABRT indicates the JVM crashed and triggers hs_err crash log generation. SIGKILL cannot be intercepted and terminates immediately. Signal handling is platform-dependent; Windows does not have signal equivalents for everything.
Object.finalize() is deprecated since Java 9 for several reasons: there is no guarantee when or if it runs, exceptions in finalizers can leave objects in a corrupt state, finalizers can resurrect objects (making them reachable again), and they impose performance overhead on every object allocation. Instead, use java.lang.ref.Cleaner for resources that must be cleaned up but should not block GC. For explicit resource cleanup (files, connections), use try-with-resources which guarantees cleanup at a specific point in code.
The bootstrap phase involves: First, the OS loads the JVM shared library and creates the JVM process. Second, the bootstrap classloader loads core JDK classes from rt.jar (Java 8) or the modules image (Java 9+), including java.lang.Object, java.lang.Class, and all fundamental classes. Third, the JVM initializes internal subsystems: the garbage collector, interpreter, JIT compiler infrastructure, main thread and thread group, and classloader subsystem. Fourth, command line arguments are parsed. Fifth, shutdown hooks are registered. Sixth, the application main class is loaded and its static initializers run. Finally, the main method executes.
System.exit() initiates a complete JVM shutdown sequence, which includes running all registered shutdown hooks. If called from a shutdown hook, it attempts to start a nested shutdown sequence, causing a deadlock because the JVM is already waiting for all hooks to complete. Runtime.halt() terminates the JVM immediately without running any shutdown hooks. The only way to halt without running hooks is to call halt from outside the shutdown sequence entirely. Use halt only when you need to force termination and are certain no cleanup is needed or possible.
Shutdown hooks run at most once per JVM instance because the JVM marks hooks as complete after execution. However, if the same hook thread is registered multiple times (for example, the same Runnable instance passed to addShutdownHook twice), the JVM treats them as separate hook entries and runs the hook multiple times. A shutdown hook may not run if: the JVM crashes (SIGABRT does not guarantee hook completion), the process is killed with SIGKILL, Runtime.halt() is called, or the operating system terminates the process forcefully.
In Java 9+, the module system defines a strict ordering for class loading during startup. The boot module (java.base) is loaded first by the bootstrap classloader. Other JDK modules (java.sql, java.xml, etc.) are loaded as needed based on the module graph. Application modules are loaded after the JDK modules, following the requires-dependencies declared in each module's module-info.java. This means a module that requires another cannot be loaded before its dependency. Circular dependencies between modules are disallowed at runtime.
The -XX:+HandlePromotionFailure flag controlled how the young generation collector handled promotion failures during garbage collection. A promotion failure occurred when the JVM tried to promote an object from young to old generation but found insufficient contiguous space. When enabled, the JVM would perform a "defensive" collection, copying fewer objects to old generation and keeping more in survivor spaces. This flag was significant for G1 and CMS collectors. Starting from Java 7u4, the HotSpot JVM made this behavior the default (always enabled), and the flag was eventually removed in newer Java versions.
Initialization hooks are threads that start during JVM initialization, before the application main method runs. They are useful for pre-warming caches, initializing native libraries, verifying external dependencies, or setting up JVMTI agents. Shutdown hooks are threads registered via Runtime.addShutdownHook() that run during JVM shutdown, after application threads have terminated but before process exit. They serve different lifecycle phases: startup vs. shutdown of the JVM process.
Java Flight Recorder (JFR) runs as a JVM-internal shutdown hook. When the JVM receives SIGABRT (crash) or normal shutdown, FSR dumps its recorded data to a file (`.jfr` file) before the process exits. This allows post-mortem analysis of what happened leading up to a crash. FSR data is written in a binary format that can be analyzed with JDK Mission Control. If the JVM crashes, the hs_err log and FSR dump provide complementary diagnostic information.
Phase 1: Shutdown requested (via System.exit(), signal, or all non-daemon threads ending). Phase 2: Running shutdown hooks—all registered hooks run concurrently; the JVM waits indefinitely for them to complete unless Runtime.halt() is called. Phase 3: Finalization—runs any uninvoked finalizers (deprecated in Java 9+). Phase 4: JVM termination—the JVM calls _exit() to terminate the process. Phase 5: OS-level process cleanup (kernel reclaims resources).
If a static initializer throws an exception that is not caught, the class is marked as having failed initialization. Subsequent attempts to use the class throw ExceptionInInitializerError wrapping the original exception. Other threads that were waiting for the class to initialize will also receive ExceptionInInitializerError. This prevents classes from being used in a partially-initialized state, which could cause hard-to-debug behavior. The original exception is preserved as the cause of ExceptionInInitializerError.
The JVM waits for all shutdown hooks to complete before exiting. If any hook blocks indefinitely (waiting on network I/O, file I/O, or locks held by other threads), the JVM hangs on shutdown. Hooks that take too long delay process termination, which matters for container orchestrators and service managers expecting quick restarts. Best practice: perform cleanup synchronously during normal operation, not asynchronously at shutdown where hooks may never run (SIGKILL, crash).
The VMThread is the internal JVM thread responsible for executing VM operations, including shutdown sequence execution. When shutdown is triggered, the VMThread coordinates the shutdown hooks, finalization, and other VM-level cleanup tasks. It ensures these run in the proper order on dedicated hook threads. The VMThread is not directly accessible from Java code—it is internal to the JVM implementation.
During initialization, the JVM must ensure the core classes are properly loaded and linked before any user code runs. The bootstrap classloader loads java.lang.Object, java.lang.Class, and all fundamental types. Static fields get default values, and static initializers run in a specific order. The JVM verifies that core classes pass bytecode verification before executing any of their methods. This creates a trusted foundation on which all other Java code depends.
If the same Thread or Runnable instance is registered twice via addShutdownHook(), the JVM treats them as two separate hook entries. Both will run during shutdown, potentially causing duplicate cleanup operations. Design hooks to be idempotent if possible, or guard against double execution with a flag. The JVM does not deduplicate hook registrations.
The -XX:+TraceClassInitialization flag prints a log line for each class as it is initialized, including the timestamp and any exception that occurred. This helps diagnose circular class initialization deadlocks or failures during startup. When classes fail to initialize, the trace shows which class's static initializer threw, allowing you to pinpoint the problem. Combine with GC logs for a complete picture of JVM startup behavior.
Daemon threads do not prevent the JVM from shutting down. When all non-daemon threads have terminated, the JVM proceeds to the shutdown hook phase regardless of daemon threads still running. However, daemon threads may be terminated at any point during shutdown hook execution, which can cause problems if hooks depend on daemon threads for cleanup. Do not rely on daemon threads being alive during shutdown hooks.
Further Reading
- JVM Architecture Overview - How startup fits into the overall JVM
- Runtime Data Areas - Memory regions initialized during bootstrap
- Class Loader Subsystem - Class loading order during bootstrap
- Execution Engine - How the JIT warms up during startup
- Advanced Java & JVM Internals Roadmap - Structured learning path
Conclusion
JVM startup involves loading core classes, initializing the runtime system (GC, JIT, threads), and executing your main method. Shutdown runs registered hooks concurrently before finalization and process exit. Implement graceful shutdown by registering hooks that clean up resources, never call System.exit() from a hook, and keep hooks fast and non-blocking. SIGKILL bypasses all shutdown handling.
Category
Related Posts
GraalVM Native Image
Understand ahead-of-time compilation with GraalVM Native Image and the Substrate VM runtime for near-instant startup and minimal memory footprint.
Async-Profiler: Low-Overhead CPU and Memory Profiling
Learn async-profiler for low-overhead CPU and memory profiling in production. Generate flame graphs, analyze allocations, and diagnose JVM bottlenecks.
Java Atomics and VarHandle: Low-Level Concurrency
Understanding Java atomic operations: AtomicInteger, AtomicReference, VarHandle, compareAndSet, atomics vs locks, and lock-free programming patterns.