深入JVM关闭与关闭钩子

本文详细介绍了JVM的关闭方式,包括正常关闭、强制关闭和异常关闭,并重点探讨了JVM的关闭钩子(shutdown hook)。关闭钩子是一种在JVM关闭时执行扫尾工作的方式,如删除临时文件、停止服务等。文章解析了Runtime和ShutdownHook的源码,强调了在编写钩子时需要注意的事项,并给出了关闭钩子的综合应用场景,包括内存管理、执行命令和临时文件清理等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 简述JVM关闭

通常而言,对于JVM的关闭我们很少去关注,但是了解JVM的关闭能帮我们在JVM关闭时做一些合理的事情。首先JVM的关闭方式可以分为三种:

  1. 正常关闭:当最后一个非守护线程结束或者调用了System.exit或者通过其他特定平台的方法关闭(发送SIGINT,SIGTERM信号等)
  2. 强制关闭:通过调用Runtime.halt方法或者是在操作系统中直接kill(发送SIGKILL信号)掉JVM进程
  3. 异常关闭:运行中遇到RuntimeException异常等。

在某些情况下,我们需要在JVM关闭时做些扫尾的工作,比如删除临时文件、停止日志服务以及内存数据写到磁盘等,为此JVM提供了关闭钩子(shutdown hooks)来做这些事情。另外特别注意的是:如果JVM因异常关闭,那么子线程(Hook本质上也是子线程)将不会停止。但在JVM被强行关闭时,这些线程都会被强行结束。

在了解关闭钩子之前我们首先认识Runtime。Runtime封装Java应用运行时的环境。通过Runtime实例,使得应用程序和其运行环境相连接。Runtime是在应用启动期间自动建立,应用程序不能够创建Runtime,但是我们可以通过Runtime.getRuntime()来获得当前应用的Runtime对象引用,通过该引用我们可以获得当前运行环境的相关信息,比如空闲内存、最大内存以及为当前虚拟机添加关闭钩子(addShutdownHook()),执行指定命令(exec())等等。


2. 深入关闭钩子(shutdown hook)

关闭钩子本质上是一个线程(也称为Hook线程),用来监听JVM的关闭。通过使用Runtime的addShutdownHook(Thread hook)可以向JVM注册一个关闭钩子。Hook线程在JVM 正常关闭才会执行,在强制关闭时不会执行。

对于一个JVM中注册的多个关闭钩子它们将会并发执行,所以JVM并不能保证它的执行顺行。当所有的Hook线程执行完毕后,如果此时runFinalizersOnExit为true,那么JVM将先运行终结器,然后停止。Hook线程会延迟JVM的关闭时间,这就要求在编写钩子过程中必须要尽可能的减少Hook线程的执行时间。另外由于多个钩子是并发执行的,那么很可能因为代码不当导致出现竞态条件或死锁等问题,为了避免该问题,强烈建议在一个钩子中执行一系列操作。

另外在使用关闭钩子还要注意以下几点:
1. 不能在钩子调用System.exit(),否则卡住JVM的关闭过程,但是可以调用Runtime.halt()
2. 不能再钩子中再进行钩子的添加和删掉操作,否则将会抛出IllegalStateException。
3. 在System.exit()之后添加的钩子无效。
4. 当JVM收到SIGTERM命令(比如操作系统在关闭时)后,如果钩子线程在一定时间没有完成,那么Hook线程可能在执行过程中被终止。
5. Hool线程中同样会抛出异常,如果抛出异常又不处理,那么钩子的执行序列就会被停止。

源码解析

现在我们来通过源码来简单的分析一下shutdown hook的实现原理。简单的说,Shutdown类充当触发器,负责触发关闭钩子,而ApplicationShutdownHooks负责管理所有注册到JVM的关闭钩子。

1. Shutdown源码分析

class Shutdown {

    /* 定义了三个状态码,用来表示关闭状态,也是System.exit(state)中的state的取值范围 */
    private static final int RUNNING = 0;
    private static final int HOOKS = 1;
    private static final int FINALIZERS = 2;
    private static int state = RUNNING;//初始化为运行状态

    //在JVM退出前是否要运行终结器?
    private static boolean runFinalizersOnExit = false;

    // 三种预定义的钩子类型,如下:
    // (0) Console restore hook
    // (1) Application hooks  (ApplicationShutdownHooks中添加的就是该类型)
    // (2) DeleteOnExit hook
    private static final int MAX_SYSTEM_HOOKS = 10;
    private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];

    // the index of the currently running shutdown hook to the hooks array
    private static int currentRunningHook = 0;

    /* The preceding static fields are protected by this lock */
    private static class Lock {
    };
    private static Object lock = new Lock();

    /* Lock object for the native halt method */
    private static Object haltLock = new Lock();

    /* Invoked by Runtime.runFinalizersOnExit */
    static void setRunFinalizersOnExit(boolean run) {
        synchronized (lock) {
            runFinalizersOnExit = run;
        }
    }

    //添加钩子,每种类型的钩子只能添加一次。ApplicationShutdownHooks类用于管理应用程序添加的钩子,通过static{}在初始化阶段添加。
    static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
        synchronized (lock) {
            if (hooks[slot] != null)//每个钩子只能被添加一次
                throw new InternalError("Shutdown hook at slot " + slot + " already registered");

            if (!registerShutdownInProgress) {
                if (state > RUNNING)
                    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值