从JDK源码看关闭钩子

关闭钩子

Java提供了Shutdown Hook机制,它让咱们在程序正常退出或者发生异常时能有机会作一些清场工做。使用的方法也很简单,Java.Runtime.addShutdownHook(Thread hook)便可。关闭钩子其实能够当作是一个已经初始化了的但还没启动的线程,当JVM关闭时会并发地执行注册的全部关闭钩子。html

钩子执行时机

向JVM注册关闭钩子后的何时会被调用,何时不会被调用呢?分红如下状况:java

  • Java程序正常运行完退出时会被调用。
  • windows和linux终端中经过ctrl-c终止命令时会被调用。
  • JVM发生OutOfMemory而退出时会被调用。
  • Java程序中执行System.exit()时会被调用。
  • 操做系统关闭时会被调用。
  • linux经过kill pid(除了kill -9 pid)结束进程时会被调用。
  • windows直接结束进程时不会被调用。

添加删除钩子

钩子的添加和删除都是经过 Runtime 来实现,里面的实现也比较简单,能够看到 addShutdownHook 和 removeShutdownHook 方法都是先经过安全管理器先检查是否有 shutdownHooks 的权限,而后再经过 ApplicationShutdownHooks 添加和删除钩子。linux

public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }

public boolean removeShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        return ApplicationShutdownHooks.remove(hook);
    }复制代码

ApplicationShutdownHooks保管钩子

ApplicationShutdownHooks 能够当作是用来保管全部关闭钩子的容器,而主要是经过一个 IdentityHashMap 类型的变量来保存钩子。windows

private static IdentityHashMap<Thread, Thread> hooks;复制代码

有了 hooks 这个变量,添加删除钩子就是直接向这个 HashMap 进行 put 和 remove 操做了,其中在操做前也会作一些检查,好比添加钩子前会作三个判断:数组

  1. 全部钩子是否已经开始执行了,hooks 为 null 即表示全部关闭钩子已经开始执行,此时不能再添加了。
  2. 钩子状态是否为 alive ,是则表示钩子已经在运行,不能添加了。
  3. 是否已经包含了该钩子,已包含则不能再添加。

相似的判断逻辑还有 remove 操做。安全

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;
    }复制代码

而 ApplicationShutdownHooks 中真正负责启动全部钩子的任务由 runHooks 方法负责,它的逻辑以下:bash

  1. 先对 ApplicationShutdownHooks 类加锁并取到全部钩子,而后将 hooks 变量设为 null 。
  2. 遍历全部钩子,分别启动钩子,前面有说到关闭钩子其实能够当作是一个已经初始化了的但还没启动的线程,这里调用 start 方法将其启动便可。
  3. 用 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) { }
         }
     }复制代码

ApplicationShutdownHooks 的 runHooks 方法又是由谁负责调用的呢?以下,它实际上是变成一个 Runnable 对象添加到 Shutdown 类中了,Runnable 的 run 方法负责调用 runHooks 方法。接下去就要看 Shutdown 类何时执行该 Runnable 对象了。优化

Shutdown.add(1 , false ,
                new Runnable() {
                    public void run() {
                        runHooks();
                    }
                }
            );复制代码

Shutdown中的钩子

ApplicationShutdownHooks 的 Runnable 对象添加到 Shutdown 中的逻辑以下,ui

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

private static final int MAX_SYSTEM_HOOKS = 10;
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];

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)
                    throw new IllegalStateException("Shutdown in progress");
            } else {
                if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
                    throw new IllegalStateException("Shutdown in progress");
            }

            hooks[slot] = hook;
        }
    }复制代码

slot表示将Runnable对象赋给 hooks 数组中的哪一个元素中, Shutdown 中一样有一个 hooks 变量,它是 Runnable[] 类型,长度为 MAX_SYSTEM_HOOKS ,即为 10 。这个数组能够当作是钩子的优先级实现,数组下标用于表示优先级,slot = 1 则表示赋值到数组中第二个元素。

registerShutdownInProgress 表示是否容许注册钩子,即便正在执行 shutdown 。前面传入 false ,显然是不容许。其中 state > RUNNING 条件表示其余状态都要抛出异常,除非是 RUNNING 状态,这个很好理解,一共有三个状态,RUNNING、HOOKS、FINALIZERS,值分别为0、一、2。若是 registerShutdownInProgress 为 true 则只要不为 FINALIZERS 状态,同时 slot 也要大于当前钩子数组的下标便可。

在前面说到的钩子执行时机的状况下,JVM都会调用到 Shutdown 类的 sequence 方法,以下,

private static void sequence() {
        synchronized (lock) {
            if (state != HOOKS) return;
        }
        runHooks();
        boolean rfoe;
        synchronized (lock) {
            state = FINALIZERS;
            rfoe = runFinalizersOnExit;
        }
        if (rfoe) runAllFinalizers();
    }

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) {
                if (t instanceof ThreadDeath) {
                    ThreadDeath td = (ThreadDeath)t;
                    throw td;
                }
            }
        }
    }复制代码

首先判断当前状态不等于 HOOKS 则直接返回,接着执行 runHooks 方法,这个方法也是咱们主要要看的方法。而后再将状态设为 FINALIZERS ,最后若是须要的话还要调用 runAllFinalizers 方法执行全部 finalizer。因此在JVM关闭时 runHooks 方法是会被调用的。

runHooks 方法逻辑简单,就是遍历 Runnable 数组,一个个调用其 run 方法让其执行。

如下是广告相关阅读

========广告时间========

鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有须要的朋友能够到 item.jd.com/12185360.ht… 进行预约。感谢各位朋友。

为何写《Tomcat内核设计剖析》

=========================

相关阅读:

从JDK源码角度看Object
从JDK源码角度看Long
从JDK源码角度看Integer
从JDK源码角度看Float
volatile足以保证数据同步吗
谈谈Java基础数据类型
从JDK源码角度看并发锁的优化
从JDK源码角度看线程的阻塞和唤醒
从JDK源码角度看并发竞争的超时
从JDK源码角度看java并发线程的中断
从JDK源码角度看Java并发的公平性
从JDK源码角度看java并发的原子性如何保证
从JDK源码角度看Byte
从JDK源码角度看Boolean
从JDK源码角度看Short
从JDK源码看System.exit

欢迎关注:

这里写图片描述
这里写图片描述

这里写图片描述
这里写图片描述
相关文章
相关标签/搜索