写在前面
最近看众多框架源码的时候都看到使用到了Shutdown Hook机制。比如下图:SkyWalking、Spring、Tomcat等等框架,几乎只要是Java层面的框架都会使用到此机制。所以,借用论坛给读者写一篇关于JVM Shutdown Hook 机制原理分析以及源码分析。
Shutdown Hook 机制原理:
这里就不提供代码案例展示了,因为上面几个框架源码已经展示的很明显了。
JVM提供的一个hook机制,在JVM关闭之前JVM自动触发开发者实现的hook。开发者可以在运行期间动态添加或者删除hook。此机制目的也很简单,让开发者可以在JVM关闭之前做收尾工作,合理的释放自己想释放的资源,而不是全部委托给JVM来释放。 大致流程图如下:
源码分析:
从上述的大致流程图可以分析得出源码分为2个步骤:
- Java层面如何注册ShutdownHook任务
- JVM层面如何回掉所有的ShutdownHook任务
先从读者都能看懂的Java层面入手
Runtime.getRuntime().addShutdownHook(new Thread(()-> System.out.println("shutdownHook..")));
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
这里调用了ApplicationShutdownHooks.add方法,把传入的Thread线程添加进去。
class ApplicationShutdownHooks {
private static IdentityHashMap<Thread, Thread> hooks;
static {
try {
// 调用Shutdown类的静态方法add,注册一个runnable任务。
Shutdown.add(1
,false,
new Runnable() {
public void run() {
// runnable回掉runHooks方法。
runHooks();
}
}
);
hooks = new IdentityHashMap<>();
}
}
// 传入线程和任务体
static synchronized void add(Thread hook) {
…………
// 添加到全局集合中,等待被回掉。
hooks.put(hook, hook);
}
// 回掉方法。
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
// 执行注册的所有的hook。
for (Thread hook : threads) {
hook.start();
}
// 等待所有的hook执行完毕。
for (Thread hook : threads) {
while (true) {
try {
hook.join();
break;
} catch (InterruptedException ignored) {
}
}
}
}
}
这里描述出来可能比较绕,因为存在多次回掉,笔者认为代码不难,应该读者都能够自己看明白。不过这里还是对以上代码做一个总结。
- Runtime.getRuntime().addShutdownHook方法调用了ApplicationShutdownHooks.add(hook); 把线程对象传入。
- add方法内部非常简单把线程对象添加到全局的hooks集合中(注意这里的一切都是static修饰的,所以是类共享的,也即是唯一的)
- ApplicationShutdownHooks类中static静态代码块中调用Shutdown.add方法,注册了一个Runnable任务。
- Runnable任务的run任务体执行的是runHooks方法,也即最终会回掉runHooks方法。
- runHooks方法就非常的明显了,启动全局hooks线程集合的线程对象(这也对应上第一点),并且此处阻塞直到所有的线程执行完毕。
- 所以接下来需要分析Shutdown类做了些什么。
class Shutdown {
// 全局的Runnable数组
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
synchronized (lock) {
…………
// 添加到全局数组中
hooks[slot] = hook;
}
}
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)
// 回掉Runnable,也即执行ApplicationShutdownHooks类中runHooks方法
hook.run();
}
}
}
private static void sequence() {
synchronized (lock) {
if (state != HOOKS) return;
}
// 执行runHooks方法
runHooks();
boolean rfoe;
synchronized (lock) {
state = FINALIZERS;
rfoe = runFinalizersOnExit;
}
if (rfoe) runAllFinalizers();
}
static void shutdown() {
synchronized (lock) {
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and then return */
case FINALIZERS:
break;
}
}
synchronized (Shutdown.class) {
// 执行sequence方法
sequence();
}
}
}
Shutdown类中add方法接收ApplicationShutdownHooks类static静态代码块传入的Runnable任务,添加到全局的Runnable数组中。在Shutdown类中shutdown方法回掉sequence方法,而在sequence方法中回掉runHooks方法,而在Shutdown类中runHooks方法回掉Runnable任务。而回掉Runnable任务就是在执行执行ApplicationShutdownHooks类中runHooks方法。而执行ApplicationShutdownHooks类中runHooks方法就等于在启动开发者传入的Thread线程对象,最终执行开发者的自定义逻辑。
所以接下来只需要找到Shutdown类中shutdown方法调用处就全部闭环啦。到此,可能对于大部分的Java程序员找破头都找不出来哪里调用的。没错,这里是JVM调用的,所以接下来是JVM源码部分。
bool Threads::destroy_vm() {
JavaThread* thread = JavaThread::current();
{
MutexLocker nu(Threads_lock);
// 这里对应上一句八股文,主线程执行完毕后,JVM关闭需要等待其他非守护线程执行完毕。
while (Threads::number_of_non_daemon_threads() > 1 )
// 阻塞等待。
Threads_lock->wait(!Mutex::_no_safepoint_check_flag, 0,
Mutex::_as_suspend_equivalent_flag);
}
…………
// JDK12的特殊处理
if (JDK_Version::is_jdk12x_version()) {
HandleMark rm(thread);
Universe::run_finalizers_on_exit();
} else {
// 执行Java程序注册的ShutdownHooks。
thread->invoke_shutdown_hooks();
}
thread->exit(true);
…………
return true;
}
这里是JVM执行完main主线程后摧毁main主线程的逻辑代码。
也非常的简单,这里需要阻塞等待其他非守护线程执行完毕,然后执行invoke_shutdown_hooks方法去回掉Java程序注册的ShutdownHooks逻辑。
// 来自vmSymbols.hpp 映射表
template(shutdown_method_name, "shutdown")
template(void_method_signature, "()V")
void JavaThread::invoke_shutdown_hooks() {
…………
// 拿到java_lang_Shutdown类对象。也即拿到Shutdown类对象。
Klass* k =
SystemDictionary::resolve_or_null(vmSymbols::java_lang_Shutdown(),
THREAD);
if (k != NULL) {
instanceKlassHandle shutdown_klass (THREAD, k);
JavaValue result(T_VOID);
// 调用Shutdown类的shutdown方法。
JavaCalls::call_static(&result,
shutdown_klass,
vmSymbols::shutdown_method_name(),
vmSymbols::void_method_signature(),
THREAD);
}
}
到此,就全部闭环啦~!
总结:
大部分笔者在Java层面如鱼得水,但是C/C++层面无从下手。所以想扩宽道路还是得多往底层学习。
ShutdownHook机制并不难,使用起来也是非常的简单,所以没啥好总结的~!
最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和框架的源码解读~!