鬣狗技术-Java_ShutdownHook你会注册“钩子”嘛

2022-03-12 00:03栏目:编程
TAG:

作者:鬣狗

日期:2021年4月11日

0.简介

ShutdownHook中文翻译“关闭钩子”或“关闭挂钩” ,以下均使用”关闭挂钩“。这个名词来进行说明。”关闭挂钩“的使用场景是在应用程序退出前执行的一些额外工作。比如应用程序在运行时创建了一些临时文件,或者在内存中缓存了一些额外的数据,那么在应用程序退出前需要将临时文件删除,或者将内存中中缓存的数据刷写到硬盘中。这些动作可以通过”关闭挂钩“来执行。相比于应用程序直接关闭退出,”关闭挂钩“机制使得程序的退出更加的平滑,优雅(Graceful)。

1.示例

注册关闭挂钩其实相当简单。


图1 注册关闭挂钩

如图所示,通过Runtime类获取到Runtime对象,然后使用addShutDownHook函数就可以注册”钩子“。然后可以看到程序正常执行退出时,”关闭挂钩“被执行。

注册”关闭挂钩“其实非常简单,接下来从源码角度分析下是怎么实现的。

2.源码分析

从Runtime.addShutdownHook函数入手


图2 Runtime.addShutdownHook

可以看到,Runtime.addShutdownHook函数首先使用SecurityManager(Java安全策略管理器,这个在今后会说)进行权限校验,然后委派给
ApplicationShutdownHooks.add方法。

ApplicaitonShutdownHooks

图3 ApplicationShutdownHooks.add

ApplicationShutdownHooks将注册的“关闭挂钩”放在一个IdentityHashMap中。

关闭挂钩执行

上面介绍的是关闭挂钩的注册,下面说明“关闭挂钩”在程序退出时是怎么被执行的。


图4 ApplicationShutdownHooks static块

ApplicationShutdownHooks通过Shutdown类将自己具体执行“关闭挂钩”的方法(即runHooks)注册进去。


图5 runHooks

runHooks就是具体执行“关闭挂钩”的函数,可以看到就是将“关闭挂钩”全部启动,然后让当前线程等待“关闭挂钩”全部执行结束。

这里面注意的一个点就是被红色线框起来的代码,这段代码很有借鉴性,第一个for循环将所有的线程启动,第二个for循环让当前线程等待关闭钩子的执行。

注意:在Tomcat的源码中也会看到有相应的思想(场景:tomcat架构是以Container为单位的,tomcat关闭时是逐个将每个Container层进行关闭的,在之后会开一个Tomcat专栏,分析tomcat架构和源码)。


Shutdown

ApplicationShutdownHooks的static块中使用了Shutdown类。ShutDown类是具体执行Java应用程序退出逻辑的类。


图6 Shutdown.add

图7 hooks

ShutDown函数将“关闭挂钩”注册到内部的一个Runnabe数组中,这里面每一个位置称作槽(slot),就是卡槽。其中0号,1号,2号卡槽分别分配给了 Console restore hook,ApplicationShutdown hooks,DeleteOnExit hook。所以在类ApplicationShutdownHooks,DeleteOnExitHook,Console类的静态代码块中都可以看到调用ShutDown函数注册的代码。卡槽的数量最多个10个。


图8 Console

图9 DeleteOnExitHook

可以看到这两个类在静态代码块中都调用Shutdown类进行“关闭挂钩”的注册,这里面这两个类的具体作用不进行说明。

/* Invoked by Runtime.exit, which does all the security checks. * Also invoked by handlers for system-provided termination events, * which should pass a nonzero status code. */ static void exit(int status) { boolean runMoreFinalizers = false; synchronized (lock) { if (status != 0) runFinalizersOnExit = false; switch (state) { case RUNNING: /* Initiate shutdown */ state = HOOKS; break; case HOOKS: /* Stall and halt */ break; case FINALIZERS: if (status != 0) { /* Halt immediately on nonzero status */ halt(status); } else { /* Compatibility with old behavior: * Run more finalizers and then halt */ runMoreFinalizers = runFinalizersOnExit; } break; } } if (runMoreFinalizers) { runAllFinalizers(); halt(status); } synchronized (Shutdown.class) { /* Synchronize on the class object, causing any other thread * that attempts to initiate shutdown to stall indefinitely */ sequence(); halt(status); } } private static void sequence() { synchronized (lock) { /* Guard against the possibility of a daemon thread invoking exit * after DestroyJavaVM initiates the shutdown sequence */ if (state != HOOKS) return; } runHooks(); boolean rfoe; synchronized (lock) { state = FINALIZERS; rfoe = runFinalizersOnExit; } if (rfoe) runAllFinalizers(); } /* Run all registered shutdown hooks */ private static void runHooks() { for (int i=0; i < MAX_SYSTEM_HOOKS; i++) { try { Runnable hook; synchronized (lock) { // acquire the lock to make sure the hook registered during // shutdown is visible here. currentRunningHook = i; hook = hooks[i]; } if (hook != null) hook.run(); } catch(Throwable t) { if (t instanceof ThreadDeath) { ThreadDeath td = (ThreadDeath)t; throw td; } } } }


通过上面的代码块方法调用链可以看到,程序执行exit方法退出时,最终会调用runHooks方法。而runHooks方法会把“卡槽”中的“关闭挂钩”依次执行。

相关类整体逻辑图


图10 整体逻辑

如图Shutdown作为具体注册“关闭挂钩”和执行退出的类在最底层,然后Console,ApplicationShutdownHooks , DeleteOnExitHook类在其静态代码块进行注册钩子,然后在Shutdown类上层的Runtime,和System对其功能进行封装。


3.触发时机

以下有几种场景会触发“关闭挂钩”的执行:

程序正常退出调用System.exit方法退出,这个上面已经分析过了,在方法的调用链上会调用Shutdown类中的runHooks方法。在终端使用CTRL+C让程序停止。应用程序中发生OOM错误,导致程序退出。

4.其它
鬣狗技术社区:健康生活,深度思考,硬核技术,技术交流、答疑

github主页:


https://github.com/youngFF

git仓库地址:


https://github.com/youngFF/MyHearthStone.git

gitbook地址:



欢迎各位加入鬣狗技术社区,希望能够为您提供有思考、有深度的文章。欢迎加入我们,如果你也有想法在鬣狗技术社区发表文章,头条私聊即可。


求 关注➕转发➕点赞,谢谢各位!您的支持就是我们更新的动力

本文来自网络,不代表山斋月平台立场,转载请注明出处: https://www.shanzhaiyue.top