稀土 2017-11-29
Java提供了Shutdown Hook机制,它让我们在程序正常退出或者发生异常时能有机会做一些清场工作。使用的方法也很简单,Java.Runtime.addShutdownHook(Thread hook)
即可。关闭钩子其实可以看成是一个已经初始化了的但还没启动的线程,当JVM关闭时会并发地执行注册的所有关闭钩子。
向JVM注册关闭钩子后的什么时候会被调用,什么时候不会被调用呢?分成以下情况:
钩子的添加和删除都是通过 Runtime 来实现,里面的实现也比较简单,可以看到 addShutdownHook 和 removeShutdownHook 方法都是先通过安全管理器先检查是否有 shutdownHooks 的权限,然后再通过 ApplicationShutdownHooks 添加和删除钩子。
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 可以看成是用来保管所有关闭钩子的容器,而主要是通过一个 IdentityHashMap 类型的变量来保存钩子。
private static IdentityHashMap<Thread, Thread> hooks;
有了 hooks 这个变量,添加删除钩子就是直接向这个 HashMap 进行 put 和 remove 操作了,其中在操作前也会做一些检查,比如添加钩子前会做三个判断:
类似的判断逻辑还有 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 方法负责,它的逻辑如下:
用 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(); } } );
ApplicationShutdownHooks 的 Runnable 对象添加到 Shutdown 中的逻辑如下,
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、1、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
欢迎关注:
这里写图片描述