Java之ShutdownHook

使用场景
  • 关闭资源
  • 保存程序状态
  • MQ消费安全
如何使用
Runtime.getRuntime().addShutdownHook(new Thread(){
    @Override
    public void run() {
        System.out.println("I'm shutdown hook...");
    }
});
在中间件和框架中的使用
RocketMq release-4.3.0
// org.apache.rocketmq.broker.BrokerStartup#createBrokerController
public static BrokerController createBrokerController(String[] args) {
    ...
    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
        private volatile boolean hasShutdown = false;
        private AtomicInteger shutdownTimes = new AtomicInteger(0);

        @Override
        public void run() {
            synchronized (this) {
                log.info("Shutdown hook was invoked, {}", this.shutdownTimes.incrementAndGet());
                if (!this.hasShutdown) {
                    this.hasShutdown = true;
                    long beginTime = System.currentTimeMillis();
                    controller.shutdown();
                    long consumingTimeTotal = System.currentTimeMillis() - beginTime;
                    log.info("Shutdown hook over, consuming total time(ms): {}", consumingTimeTotal);
                }
            }
        }
    }, "ShutdownHook"));
}
实现原理

1、当我们添加一个ShutdownHook时,会调用ApplicationShutdownHooks.add(hook),往ApplicationShutdownHooks类下的静态变量private static IdentityHashMap<Thread, Thread> hooks添加一个hook,hook本身是一个thread对象
2、ApplicationShutdownHooks类初始化时会把hooks添加到Shutdown的hooks中去

// java.lang.ApplicationShutdownHooks
private static IdentityHashMap<Thread, Thread> hooks;
static {
    try {
        Shutdown.add(1 /* shutdown hook invocation order */,
            false /* not registered if shutdown in progress */,
            new Runnable() {
                public void run() {
                    runHooks();
                }
            }
        );
        hooks = new IdentityHashMap<>();
    } catch (IllegalStateException e) {
        // application shutdown hooks cannot be added if
        // shutdown is in progress.
        hooks = null;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GuVH9T7Y-1675508025694)(evernotecid://4143277E-C9B7-4F5C-948C-63A95AD76A67/appyinxiangcom/20976546/ENNote/p880?hash=b01f3a2b036137a6231593be6ad05a5a)]

系统Shutdown仅仅支持10个slot
系统设定 slot=1为应用注册的hook

// java.lang.Shutdown
// The system shutdown hooks are registered with a predefined slot.
// The list of shutdown hooks is as follows:
// (0) Console restore hook
// (1) Application hooks
// (2) DeleteOnExit hook
private static final int MAX_SYSTEM_HOOKS = 10;

顺序遍历每个slot,并发执行每个slot里面注册的hooks
private static IdentityHashMap<Thread, Thread> hooks;

// java.lang.Shutdown#runHooks
private static void runHooks() {
    for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
        try {
            Runnable hook;
            synchronized (lock) {
                // acquire the lock to make sure the hook registered during
                // shutdown is visible here.
                currentRunningHook = i;
                hook = hooks[i];
            }
            if (hook != null) hook.run();
        } catch(Throwable t) {
            if (t instanceof ThreadDeath) {
                ThreadDeath td = (ThreadDeath)t;
                throw td;
            }
        }
    }
}

并发执行每个slot里面注册的hooks

// java.lang.ApplicationShutdownHooks#runHooks
static void runHooks() {
    Collection<Thread> threads;
    synchronized(ApplicationShutdownHooks.class) {
        threads = hooks.keySet();
        hooks = null;
    }

    for (Thread hook : threads) {
        hook.start();
    }
    for (Thread hook : threads) {
        while (true) {
            try {
                hook.join();
                break;
            } catch (InterruptedException ignored) {
            }
        }
    }
}
ShutdownHook触发点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KNbaoL51-1675508025695)(evernotecid://4143277E-C9B7-4F5C-948C-63A95AD76A67/appyinxiangcom/20976546/ENNote/p880?hash=bbef4e2bbd683b4ecd011a9c04e4ca08)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4CWxD8IE-1675508025695)(evernotecid://4143277E-C9B7-4F5C-948C-63A95AD76A67/appyinxiangcom/20976546/ENNote/p880?hash=c960431444d0027391dd6a911d14b6f3)]

Shutdown.exit

Shutdown.exit的调用方,发现有 Runtime.exit 和 Terminator.setup

  • Runtime.exit
    代码中主动结束进程的接口
  • Terminator.setup
    Terminator.setup 被 initializeSystemClass 调用,当第一个线程被初始化的时候被触发,触发后注册了一个信号监控函数,捕获kill发出的信号,调用Shutdown.exit结束进程
    实现SignalHandler接口以及handle方法,程序入口处注册要监听的相应信号
// java.lang.Terminator
static void setup() {
    if (handler == null) {
        SignalHandler var0 = new SignalHandler() {
            public void handle(Signal var1) {
                Shutdown.exit(var1.getNumber() + 128);
            }
        };
        handler = var0;

        try {
            Signal.handle(new Signal("HUP"), var0);
        } catch (IllegalArgumentException var4) {
        }

        try {
            Signal.handle(new Signal("INT"), var0);
        } catch (IllegalArgumentException var3) {
        }

        try {
            Signal.handle(new Signal("TERM"), var0);
        } catch (IllegalArgumentException var2) {
        }

    }
}
Shurdown.shutdown()

该方法会在最后一个非daemon线程(非守护线程)结束时被JNI的DestroyJavaVM方法调用。

注意:当我们杀死进程时使用了kill -9时,由于程序无法捕获处理,进程被直接杀死,所以无法执行ShutdownHook。

参考:
1、ShutdownHook原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值