JVM关闭钩子(2)—— 源码浅析

本文详细介绍了Java中关闭钩子的实现原理,包括Shutdown类的状态管理与钩子执行流程,以及ApplicationShutdownHooks如何维护用户级别的关闭钩子。

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

        上一篇《JVM关闭钩子(1)—— 概念和应用》简单解释了什么是钩子,并且举例说明了Java中关闭钩子的使用,现在我们来深入了解,Java中怎么实现这个关闭钩子的。

        主要通过java.lang.Shutdown和java.lang.ApplicationShutdownHooks 这两个工具类来实现。Shutdown负责触发钩子,而ApplicationShutdownHooks负责维护钩子。    【此处只介绍用户等级的关闭钩子】


java.lang.Shutdown

Java中的Shutdown类,是一个包私有的工作类,用于顺序关闭JVM,并触发关闭钩子。

 

Shutdown中定义了三个状态,用于校验关闭时机。初始是运行状态RUNNING。

    private static final int RUNNING = 0;
    private static final int HOOKS = 1;
    private static final int FINALIZERS = 2;
    private static int state = RUNNING;

 

Shutdown初始定义了一个Runnable数组hooks,其中每个元素代表一个关闭钩子类型,一般我们应用程序添加的,都为应用类型(1),在下面介绍ApplicationShutdownHooks会提到。

    // (0)Console restore hook
    // (1)Application hooks
    // (2)DeleteOnExit hook
    private static final int MAX_SYSTEM_HOOKS = 10;
    private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];


添加钩子

每种类型的钩子只能添加一次,否则会抛出异常,且每次添加的时候都需要校验当前状态。

static void add(int slot, booleanregisterShutdownInProgress, Runnable hook) {
        synchronized (lock) {
            if (hooks[slot] != null)       // 保证每个种类的钩子不会重复初始化
                throw new InternalError("hookat slot "+slot+" already registered");
 
// 初始状态检查
            if (!registerShutdownInProgress)
                if (state > RUNNING)
                    throw new IllegalStateException("Shutdown in progress");
            else
                if (state > HOOKS || (state == HOOKS&& slot <= currentRunningHook))
                    throw new IllegalStateException("Shutdown in progress");
 
            hooks[slot] = hook;
        }
}

 

执行钩子

执行关闭钩子,是在JVM的关闭事件触发之后。通常是下面两种情况:

A Runtime.exit(),这时候会调用Shutdown.exit();

B JNI来DestroyJVM,这时候会调用Shutdown.shutdown();

    这两个方法,最后都会调用Shutdown.sequence()来触发关闭钩子和停止JVM。

private static void sequence() {
        synchronized (lock) {
            if (state != HOOKS) return;
        }
        runHooks();          // 执行关闭钩子
        boolean rfoe;
        synchronized (lock) {
            state = FINALIZERS;
            rfoe = runFinalizersOnExit;
        }
        if (rfoe) runAllFinalizers();         // 全面停止系统
}


执行关闭钩子,遍历hooks,把所有类型的钩子都run()起来。

private static void runHooks() {
        for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
            try {
                Runnable hook;
                synchronized (lock) {
                    currentRunningHook = i;
                    hook = hooks[i];
                }
                if (hook != null) hook.run();
            } catch(Throwable t) {}
        }
}


java.lang.ApplicationShutdownHooks

用户等级的关闭钩子由ApplicationShutdownHooks来存放和维护。

在ApplicationShutdownHooks中,通过一个Map属性hooks来保存用户的关闭钩子。实际上每个关闭钩子都是一个线程,当JVM关闭事件触发的时候,就会执行钩子逻辑。

private staticIdentityHashMap<Thread, Thread> hooks;

这里使用IdentityHashMap来储存,这个map的key只比较地址,确保钩子的唯一性。即使两个对象属性完全一样,这里也会当成两个hook来处理。这个IdentityHashMap的具体实现,可以参考我的《JDK-Map源码浅析——IdentityHashMap》。

 

初始化,添加钩子类型

在ApplicationShutdownHooks加载的时候,首先会往Shutdown中add一个应用类型(1)的钩子类型线程,然后把 执行钩子逻辑的runHooks() 放入run()中等待调用。

这里ApplicationShutdownHooks为什么不实现Runnable接口,然后直接把自己add到Shutdown中呢?   因为没有必要:

因为Shutdown只关心用户的钩子,而对维护者ApplicationShutdownHooks没有兴趣,基于安全或者使用来考虑,都没有必要把ApplicationShutdownHooks整个传进去。所以这里新建一个线程,只把 runHooks()的调用 放进去,足矣。

这样一来,ApplicationShutdownHooks就不用实例化,可以做成单例,彻底变成了一个工具类,只用适度地暴露一些工具方法即可,并且通过静态属性hooks来保存钩子。 

让ApplicationShutdownHooks和钩子类型线程分离,使职能独立化简单化。

 static {
        try {
            Shutdown.add(1, false,
                new Runnable() {
                    public void run() {
                        runHooks();
                    }
                }
            );
            hooks = new IdentityHashMap<>();
        } catch (IllegalStateException e) {
            hooks = null;
        }
    }
    private ApplicationShutdownHooks() {}

    

添加/删除钩子,需要进行一些有效校验。

static synchronized void add(Thread hook) {
        if(hooks == null)
            throw new IllegalStateException("Shutdown in progress");
        if (hook.isAlive())
            throw new IllegalArgumentException("Hook already running");
        if(hooks.containsKey(hook))
            throw new IllegalArgumentException("Hook previously registered");
 
        hooks.put(hook, hook);
    }
    static synchronized boolean remove(Thread hook) {
        if(hooks == null)
            throw new IllegalStateException("Shutdown in progress");
        if (hook == null)
            throw new NullPointerException();
 
        return hooks.remove(hook) != null;
    }

 

运行钩子

运行钩子方法runHooks(),提供给钩子类型线程调用。

定义一个局部Thread来转移hooks,转移后清空hooks。能看出来,关闭钩子逻辑只会执行一次,执行后就清空了,因为JVM关闭才会触发。另外,转移和清空也有利于避免并发的冲突。

接着启动这些钩子线程,而且通过join来保证线程的同步执行。(此处恰到好处地把start和join分开写,下面讨论这样写的好处)

static void runHooks() {
        Collection<Thread> threads;
        synchronized(ApplicationShutdownHooks.class) {
            threads = hooks.keySet();
            hooks = null;        // 转移后清空
        }
        for (Thread hook : threads) {
            hook.start();
        }
        for (Thread hook : threads) {
            try {                 
                hook.join();
            } catch (InterruptedException x) { }
        }
    }


—— 源码来自于 JDK1.7


此处给大家留两个问题,大伙可以留言交流交流想法和心得 大笑

1、Shutdown.hooks为什么要用Runnable数组,而不用Thread数组呢?

2、文章最后的方法ApplicationShutdownHooks.runHooks()为什么要分别for循环Thread.start()和Thread.join()呢?为什么不把两个方法放到同一个for循环?分别有什么效果?



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值