- 钩子作用是啥
当你怕退出jvm时中断应用正在处理的任务,从而导致各种问题时。此时钩子就派上了用场。当然你直接拔电源、kill -9再牛逼的机制也不管用了。
- 怎么用
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hook shut down");
}
}));
System.out.println("123");
}
运行这段代码输出
123
hook shut down
- 啥原理
首先,将我们新实例化的线程作为参数调用ApplicationShutdownHooks的add方法
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
再来看add方法中经过一些校验之后将其放到map中,那么这个hooks的map怎么来的
private static IdentityHashMap<Thread, Thread> hooks;
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代码块首先运行,肯定就回创建map对象,此时在static中又调用了 Shutdown的add方法,我们再继续看
ApplicationShutdownHooks类
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;
}
}
会将ApplicationShutdownHooks类实例化的Runnable添加到Shutdown类的hooks中。
Shutdown类
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;
}
}
其次,经过以上步骤已经将钩子添加了,那么当jvm退出时是怎么触发的。
当我们运行上述代码时在
java.lang.ApplicationShutdownHooks#runHooks中打断点,然后如下图,
此时我们的main线程已退出,一个叫DestroyJavaVM的线程被激活,那么这个DestroyJavaVM是怎么被激活的呢。
《Java性能优化权威指南》中有如下一段解释,而上图中的调用栈也清晰的说明了调用顺序。
最后,经过以上步骤,我们添加的hook被调用,完成了退出jvm时优雅的关闭服务。