Java提供了Shutdown Hook机制,它让咱们在程序正常退出或者发生异常时能有机会作一些清场工做。使用的方法也很简单,Java.Runtime.addShutdownHook(Thread hook)
便可。关闭钩子其实能够当作是一个已经初始化了的但还没启动的线程,当JVM关闭时会并发地执行注册的全部关闭钩子。html
向JVM注册关闭钩子后的何时会被调用,何时不会被调用呢?分红如下状况:java
钩子的添加和删除都是经过 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 能够当作是用来保管全部关闭钩子的容器,而主要是经过一个 IdentityHashMap 类型的变量来保存钩子。windows
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 方法负责,它的逻辑以下:bash
用 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 中的逻辑以下,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… 进行预约。感谢各位朋友。
=========================
相关阅读:
从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
欢迎关注: