如何为JVM添加关闭钩子与简要分析
来源:互联网 发布:朋友圈文章制作软件 编辑:程序博客网 时间:2024/04/29 22:49
摘要
最近在看当当开源的数据库分库分表框架Sharding-jdbc的源码,在看ExecutorEngine类时,遇到了很多没用过的JDK api,Sharding-jdbc内部大量的使用了google的工具包Guava。在ExecutorEngine类处理多线程问题部分也同样用到的Guava下面的util.concurrent包的类进处理。而我在看google的Guava的MoreExecutors时便遇到了Runtime.getRuntime().addShutdownHook(hook)。
1、JVM的关闭钩子
JVM的关闭钩子是通过Runtime#addShutdownHook(Thread hook)方法来实现的,根据api是注解可知所谓的 shutdown hook 就是一系例的已初始化但尚未执行的线程对象。
当准备JVM停止前,这些shutdown hook 线程会被执行。以下几种情况会使这个shutdown hook调用:
程序正常退出,这发生在最后的非守护线程退出时,或者在调用 exit(等同于System.exit)方法。
为响应用户中断而终止 虚拟机,如键入 ^C;或发生系统事件,比如用户注销或系统关闭。
注册jvm关闭钩子通过Runtime.addShutdownHook(),实际调用ApplicationShutdownHooks.add()。后者维护了一个钩子集合IdentityHashMap<Thread, Thread> hooks。
<span style="font-size:18px;">public void addShutdownHook(Thread hook) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("shutdownHooks")); } ApplicationShutdownHooks.add(hook);}</span>
ApplicationShutdownHooks类初始化的时候,会调用static块注册一个线程到Shutdown类中。
static { try { Shutdown.add(1, false, new Runnable() { public void run() { runHooks(); } } ); hooks = new IdentityHashMap<>(); } catch (IllegalStateException e) { hooks = null; }}Shutdown类里也维护了一个钩子集合
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
这个集合是分优先级的(优先级就是下标数值),自定义的钩子优先级默认是1,也就是最先执行。关闭钩子最终触发就是从这个集合进行。应用关闭时,以System.exit()为例,依次调用Runtime.exit()、Shutdow.exit()。
Shutdown执行jvm退出逻辑,并维护了若干关闭状态
private static final int RUNNING = 0;// 初始状态,开始关闭private static final int HOOKS = 1;// 运行钩子private static final int FINALIZERS = 2;// 运行finalizerprivate static int state = RUNNING;static void exit(int status) { boolean runMoreFinalizers = false; synchronized (lock) {// 根据退出码status参数做不同处理 if (status != 0) runFinalizersOnExit = false;// 只有正常退出才会运行finalizer switch (state) { case RUNNING: // 执行钩子并修改状态 state = HOOKS; break; case HOOKS: // 执行钩子 break; case FINALIZERS: // 执行finalizer if (status != 0) { halt(status); // 如果是异常退出,直接退出进程。halt()底层是native实现,这时不会执行finalizer } else { // 正常退出则标记是否需要执行finalizer runMoreFinalizers = runFinalizersOnExit; } break; } } if (runMoreFinalizers) { // 如果有需要,就执行finalizer,注意只有state=FINALIZERS会走这个分支 runAllFinalizers(); halt(status); } synchronized (Shutdown.class) { // 这里执行state= HOOKS逻辑,包括执行钩子和finalizer sequence(); halt(status); }}private static void sequence() { synchronized (lock) { if (state != HOOKS) return; } runHooks();// 执行钩子,这里会依次执行hooks数组里的各线程 boolean rfoe;// finalizer逻辑 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]; } // 由于之前注册了ApplicationShutdownHooks的钩子线程,这里又会回调ApplicationShutdownHooks.runHooks if (hook != null) hook.run(); } catch(Throwable t) { if (t instanceof ThreadDeath) { ThreadDeath td = (ThreadDeath)t; throw td; } } }}static void runHooks() { Collection<Thread> threads; synchronized(ApplicationShutdownHooks.class) { threads = hooks.keySet(); hooks = null; } // 注意ApplicationShutdownHooks里的钩子之间是没有优先级的,如果定义了多个钩子,那么这些钩子会并发执行 for (Thread hook : threads) { hook.start(); } for (Thread hook : threads) { try { hook.join(); } catch (InterruptedException x) { } }}
2、Spring关闭钩子
Spring在AbstractApplicationContext里维护了一个shutdownHook属性,用来关闭Spring上下文。但这个钩子不是默认生效的,需要手动调用ApplicationContext.registerShutdownHook()来开启,在自行维护ApplicationContext(而不是托管给tomcat之类的容器时),注意尽量使用ApplicationContext.registerShutdownHook()或者手动调用ApplicationContext.close()来关闭Spring上下文,否则应用退出时可能会残留资源。
public void registerShutdownHook() { if (this.shutdownHook == null) { this.shutdownHook = new Thread() { @Override public void run() { // 这里会调用Spring的关闭逻辑,包括资源清理,bean的销毁等 doClose(); } }; // 这里会把spring的钩子注册到jvm关闭钩子 Runtime.getRuntime().addShutdownHook(this.shutdownHook); } }
2、Hadoop关闭钩子
private Set<HookEntry> hooks;static { Runtime.getRuntime().addShutdownHook( new Thread() { @Override public void run() { MGR.shutdownInProgress.set(true);// MGR是本类的单例 for (Runnable hook: MGR.getShutdownHooksInOrder()) { try { hook.run(); } catch (Throwable ex) { LOG.warn("ShutdownHook '" + hook.getClass().getSimpleName() + "' failed, " + ex.toString(), ex); } } } } ); }
这里HookEntry是hadoop封装的钩子类,HookEntry是带优先级的,一个priority属性。MGR.getShutdownHooksInOrder()方法会按priority依次(单线程)执行钩子。默认挂上的钩子就一个:org.apache.hadoop.fs.FileSystem$Cache$ClientFinalizer(priority=10),这个钩子用来清理hadoop FileSystem缓存以及销毁FileSystem实例。这个钩子是在第一次hadoop IO发生时(如FileSystem.get)lazy加载此外调用FileContext.deleteOnExit()方法也会通过注册钩子hadoop集群(非客户端)启动时,还会注册钩子清理临时路径。
4、SparkContext关闭钩子
def install(): Unit = { val hookTask = new Runnable() { // 执行钩子的回调进程,根据priority依次执行钩子 override def run(): Unit = runAll() } Try(Utils.classForName("org.apache.hadoop.util.ShutdownHookManager")) match { case Success(shmClass) => val fsPriority = classOf[FileSystem].getField("SHUTDOWN_HOOK_PRIORITY").get(null).asInstanceOf[Int] val shm = shmClass.getMethod("get").invoke(null) shm.getClass().getMethod("addShutdownHook", classOf[Runnable], classOf[Int]).invoke(shm, hookTask, Integer.valueOf(fsPriority + 30)) case Failure(_) =>// hadoop 1.x Runtime.getRuntime.addShutdownHook(new Thread(hookTask, "Spark Shutdown Hook")); }}
顺便说一下,hadoop的FileSystem实例底层默认是复用的,所以如果执行了两次fileSystem.close(),第二次会报错FileSystem Already Closed异常(即使表面上是对两个实例执行的)一个典型的场景是同时使用Spark和Hadoop-Api,Spark会创建FileSystem实例,Hadoop-Api也会创建,由于底层复用,两者其实是同一个。因为关闭钩子的存在,应用退出时会执行两次FileSystem.close(),导致报错。解决这个问题的办法是在hdfs-site.xml增加以下配置,关闭FileSystem实例复用。
<property> <name>fs.hdfs.impl.disable.cache</name> <value>true</value> </property>
- 如何为JVM添加关闭钩子与简要分析
- 深入JVM关闭与关闭钩子
- JVM里注册Spring的关闭钩子
- 钩子与数据分析
- 为程序加上“关闭钩子”(ShutdownHook)
- JVM关闭钩子(1) —— 概念和应用
- JVM关闭钩子(2)—— 源码浅析
- 随记:安全地关闭 jvm(tomcat停止钩子事件处理)
- 关闭钩子
- Intent与IntentFilter简要分析
- 为光驱添加“关闭”命令
- wxsqlite为sqlite加密的简要分析
- JVM内存管理和问题简要分析学习
- Wordpress钩子概念解释与分析
- 简要分析Uboot是如何启动内核!
- 简要分析Uboot是如何启动内核!
- 简要分析Uboot是如何启动内核!
- 简要分析Uboot是如何启动内核!
- Python 多进程 multiprocessing Pool 坑
- HttpClient 实例分享
- PowerDesigner 把Comment复制到name中和把name复制到Comment
- 转载一篇干货,Android软键盘弹出时把布局顶上去的解决方法
- 自动化测试工具和平台实现
- 如何为JVM添加关闭钩子与简要分析
- c++使用类
- 按月份去旅游
- PHP获取某一天前后任意时间
- 排序算法-快速排序
- 将字符串中的所有空格去掉,要求时间复杂度O(N)
- goquery 增加GBK支持
- java中组合与继承的区别
- mha安装部署