Walk through exactly how the JVM decides when to exit based on running threads, and how shutdown hooks, daemons, and System.exit interact during that sequence.
answer
- Two triggers: last user thread ends OR System.exit
- Daemons never delay exit
- Sequence: run+await hooks -> stop daemons -> exit
- Runtime.halt skips hooks; SIGKILL skips everything
- Hooks run concurrently, no built-in timeout
basics
~20 sThe JVM exits once no user (non-daemon) threads are left running, or when something calls System.exit(). At that point it runs all registered shutdown hooks, and only after those finish does it stop daemon threads and end the process. Daemons never delay exit; hooks get a window to clean up.
solid answer
~50 sThe JVM has two exit triggers: either the last user thread terminates normally, or code calls System.exit(code) / Runtime.exit(). Daemon threads never count toward keeping it alive, so they do not delay either trigger. Once shutdown begins, the JVM enters a defined sequence: it starts all registered shutdown hooks (each a user-context Thread) concurrently and waits for them to finish; hooks are the sanctioned place to flush and release resources. Only after all hooks complete does the JVM stop remaining daemon (and any still-running non-hook) threads, run any finalization if enabled, and exit with the status code. System.exit can itself be called from a shutdown hook only carefully (it can deadlock). Runtime.halt() bypasses the whole sequence — no hooks, no finalizers — and is the forcible last resort. External signals like SIGTERM initiate orderly shutdown (hooks run); SIGKILL does not. Understanding this lets you design where cleanup belongs: must-finish work on user threads, best-effort flush in hooks, never relying on daemon finalization.
go deeper
Knows the JVM exits when the program's main work is done or System.exit is called, and that some cleanup code can run at exit.
States the two triggers, that daemons don't delay exit, and that shutdown hooks run during shutdown for cleanup.
Orders the sequence (run+await hooks, then abandon daemons), distinguishes SIGTERM vs SIGKILL, and places cleanup appropriately across hooks/executors/AutoCloseable.
Reasons about the full lifecycle: hook concurrency/ordering hazards, no built-in hook timeout, halt() as escape hatch, deadlock from System.exit inside a hook, and codifies an application-wide shutdown contract.
## The two ways shutdown starts The JVM begins to shut down in exactly two situations: 1. **Natural exit** — the **last user (non-daemon) thread** finishes. The JVM continuously tracks how many non-daemon threads are alive; when that count reaches zero, there is no foreground work left, so it shuts down. 2. **Programmatic exit** — code calls `System.exit(code)` (which delegates to `Runtime.getRuntime().exit(code)`). This forces shutdown *regardless* of how many user threads are still running. Daemon threads are irrelevant to trigger (1): the JVM never waits for them. So you cannot keep a JVM alive with daemons alone — start a daemon-only program and it exits immediately after `main` returns. ## The orderly shutdown sequence When either trigger fires, `Runtime` runs a defined sequence (`Shutdown.sequence()` internally): 1. **Run shutdown hooks.** All hooks registered via `Runtime.getRuntime().addShutdownHook(thread)` are **started concurrently** (they are unstarted `Thread` objects you supplied). The JVM then **blocks until every hook thread has finished**. Hooks run in a user (non-daemon) context — this is the window the platform gives you to flush buffers, close files/sockets, stop services, and so on. Because hooks run concurrently, they must be thread-safe and avoid depending on each other's ordering. 2. **Stop remaining threads.** After all hooks complete, the JVM stops the rest of the threads — daemons and any non-hook user threads still alive — abruptly. This is where daemons are abandoned without unwinding. 3. **Finalization (if enabled) and exit.** Legacy finalizers may run if `runFinalizersOnExit` is set (deprecated and off by default), then the process exits with the status code. Key consequence: **shutdown hooks run, daemons do not get cleanup.** That is the architectural lever — put best-effort cleanup in hooks, never in a daemon's `finally`. ## Interactions and edge cases - **`System.exit()` from within a hook:** Calling `System.exit()` (or `addShutdownHook`) once shutdown is already in progress is problematic. Adding a hook during shutdown throws `IllegalStateException`. Calling `System.exit()` from a hook can **block/deadlock** because the shutdown sequence is already waiting on hooks; it does not start a second sequence. - **`Runtime.halt(code)`:** The forcible escape hatch. It terminates the JVM **immediately**, skipping shutdown hooks and finalizers entirely. Use only when orderly shutdown is impossible (e.g., a hook hung). - **Hook timeouts:** The JVM does **not** impose a built-in timeout on hooks — a hook that blocks forever can hang shutdown. Robust hooks bound their own work. (Container orchestrators add their own SIGTERM→SIGKILL grace period.) - **OS signals:** `SIGTERM` (the polite "please stop," e.g. `kill` or a container stop) triggers the **orderly** sequence, so hooks run. `SIGKILL` (`kill -9`) terminates the process at the OS level — the JVM never gets control, so **no** hooks, no daemons cleanup, nothing. - **Uncaught exception in the last user thread:** If the final user thread dies from an uncaught exception, that still counts as the thread terminating, so natural shutdown proceeds (after the uncaught-exception handler runs). ## Designing around the sequence A principal-level mental model for where work belongs: | Need | Mechanism | Why | |---|---|---| | Must finish before exit | user thread / `ExecutorService.shutdown()`+`awaitTermination` | keeps JVM alive / bounded drain | | Best-effort flush during shutdown | shutdown hook | runs in the orderly sequence, before daemons are killed | | Deterministic resource release | try-with-resources / `AutoCloseable` | tied to scope, not exit timing | | Safety-net native cleanup | `Cleaner` | reachability-based, not exit-based | | Throwaway background chore | daemon thread | abandonment is acceptable | | Forced immediate stop | `Runtime.halt()` | bypasses a stuck sequence | ## One-paragraph summary The JVM exits when no user threads remain or `System.exit` is called; daemons never delay this. Shutdown then runs all registered hooks concurrently and waits for them — the only sanctioned cleanup window — after which daemons are abandoned and the process ends. `Runtime.halt` and `SIGKILL` skip the sequence entirely. Place must-complete work on user threads/executors, best-effort cleanup in hooks, and treat daemons as expendable.
- What is the difference between Runtime.exit() and Runtime.halt()?exit() (what System.exit delegates to) initiates the orderly shutdown sequence — it runs all shutdown hooks and waits for them before terminating. halt() forcibly terminates the JVM immediately, skipping hooks and finalizers entirely. halt is the escape hatch when an orderly shutdown is stuck.
- If a registered shutdown hook blocks forever, what happens to JVM shutdown?Shutdown hangs. The JVM starts hooks concurrently and waits for them all to finish, with no built-in timeout, so a non-terminating hook prevents the process from exiting until something forcibly kills it (Runtime.halt or an external SIGKILL). Hooks must bound their own work.
saying these in an interview costs you the question
- Saying daemons can keep the JVM alive
- Believing shutdown hooks have a guaranteed time limit enforced by the JVM
- Assuming Runtime.halt() runs shutdown hooks
- Thinking SIGKILL gives the JVM a chance to run hooks
- Claiming System.exit() inside a hook starts a fresh, clean shutdown