什么情况会导致 JVM 退出?

#Java#面试八股

什么情况会导致 JVM 退出?

结论先行:JVM 退出的本质是「所有非守护线程结束」或「有人主动终止进程」。常见路径有四类:正常结束(main 返回且无非守护线程)、主动调用(System.exit、Runtime.halt)、异常终止(未捕获异常清空唯一非守护线程、OOM 后 ExitOnOutOfMemoryError)、外部信号(SIGTERM、SIGKILL、容器 OOMKilled)。区分它们的关键是退出码和是否执行关闭钩子(shutdown hook)。

一、退出路径对照

路径是否执行 shutdown hook典型退出码说明
main 返回且无非守护线程是0最正常的退出
System.exit(n)是n触发钩子后退出,finally 不会执行
Runtime.halt(n)否n强制退出,钩子也不跑
未捕获异常是1只有「最后一条非守护线程」被异常终结时才退出
-XX:+ExitOnOutOfMemoryError是1OOM 直接退出,交给编排系统重启
kill -15(SIGTERM)是143容器停止、docker stop 的默认信号
kill -9(SIGKILL)否137无法被捕获,进程直接消失
容器 OOMKilled否137cgroup 限内存被杀,JVM 来不及做任何事
JVM 崩溃(SIGSEGV)否134 / 139生成 hs_err_pid.log
线程视角:
  非守护线程集合为空?
    ├─ 是 → 触发 Shutdown.exit → 执行所有 shutdown hook → 终止
    └─ 否 → 继续运行(哪怕 main 早已返回)

二、非守护线程才是「活着」的标准

public class ExitByThread {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(3000);
                System.out.println("工作线程结束");
            } catch (InterruptedException ignored) {
            }
        });
        t.setDaemon(false);          // 改成 true,JVM 会立刻退出,打印不出来
        t.start();
        System.out.println("main 返回");
    }
}
// 输出:
// main 返回
// (约 3 秒后)工作线程结束

线上线程池的线程默认是非守护线程,所以只要池没关,JVM 就不会自己退出——这正是「服务停不下来」的常见原因。

三、关闭钩子与优雅停机

public class GracefulShutdown {
    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("开始优雅停机:停止接流量、提交偏移、关闭池");
            try {
                Thread.sleep(500);          // 模拟等待请求处理完
            } catch (InterruptedException ignored) {
            }
            System.out.println("钩子执行完毕");
        }, "shutdown-hook"));

        System.out.println("启动完成,等待 SIGTERM");
    }
}
java GracefulShutdown &        # 后台启动
kill -15 $!                    # SIGTERM 会触发钩子
# 输出:启动完成,等待 SIGTERM
#       开始优雅停机:停止接流量、提交偏移、关闭池
#       钩子执行完毕

钩子里不要做重活:JVM 给钩子的时间有限(容器默认 30 秒 terminationGracePeriodSeconds),超时会被 SIGKILL,钩子被打断后数据可能不一致。

四、常见追问

追问回答要点
System.exit 会执行 finally 吗不会,System.exit 内部调用 Shutdown.exit 直接终止流程,finally 被跳过
OOM 后进程一定会退出吗不一定,默认只是抛异常;只有 ExitOnOutOfMemoryError 或异常冒泡到主线程才退出
137 和 143 怎么区分137 = 128+9,被 SIGKILL 或 cgroup OOMKilled;143 = 128+15,收到 SIGTERM 正常停机
容器里为什么总是 137堆外内存(直接内存、元空间、线程栈)没算进 -Xmx,超出 cgroup limit 就被杀
怎么排查意外退出先看容器事件与 dmesg(OOMKilled),再看 hs_err_pid*.log,最后看启动参数是否配了 ExitOnOutOfMemoryError

小结

JVM 退出看两件事:非守护线程是否清空、退出码是多少。0/1 是主动路径,137/143 是外部信号,134/139 是崩溃,配合 shutdown hook 与 hs_err 日志就能还原现场。

笔记加载中…