JVM关闭钩子(2)—— 源码浅析
来源:互联网 发布:纳网科技 中文域名 编辑:程序博客网 时间:2024/05/16 16:18
上一篇《JVM关闭钩子(1)—— 概念和应用》简单解释了什么是钩子,并且举例说明了Java中关闭钩子的使用,现在我们来深入了解,Java中怎么实现这个关闭钩子的。
主要通过java.lang.Shutdown和java.lang.ApplicationShutdownHooks 这两个工具类来实现。Shutdown负责触发钩子,而ApplicationShutdownHooks负责维护钩子。 【此处只介绍用户等级的关闭钩子】
java.lang.Shutdown
Java中的Shutdown类,是一个包私有的工作类,用于顺序关闭JVM,并触发关闭钩子。
Shutdown中定义了三个状态,用于校验关闭时机。初始是运行状态RUNNING。
private static final int RUNNING = 0; private static final int HOOKS = 1; private static final int FINALIZERS = 2; private static int state = RUNNING;
Shutdown初始定义了一个Runnable数组hooks,其中每个元素代表一个关闭钩子类型,一般我们应用程序添加的,都为应用类型(1),在下面介绍ApplicationShutdownHooks会提到。
// (0)Console restore hook // (1)Application hooks // (2)DeleteOnExit hook private static final int MAX_SYSTEM_HOOKS = 10; private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
添加钩子
每种类型的钩子只能添加一次,否则会抛出异常,且每次添加的时候都需要校验当前状态。
static void add(int slot, booleanregisterShutdownInProgress, Runnable hook) { synchronized (lock) { if (hooks[slot] != null) // 保证每个种类的钩子不会重复初始化 throw new InternalError("hookat 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的关闭事件触发之后。通常是下面两种情况:
A Runtime.exit(),这时候会调用Shutdown.exit();
B JNI来DestroyJVM,这时候会调用Shutdown.shutdown();
这两个方法,最后都会调用Shutdown.sequence()来触发关闭钩子和停止JVM。
private static void sequence() { synchronized (lock) { if (state != HOOKS) return; } runHooks(); // 执行关闭钩子 boolean rfoe; synchronized (lock) { state = FINALIZERS; rfoe = runFinalizersOnExit; } if (rfoe) runAllFinalizers(); // 全面停止系统}
执行关闭钩子,遍历hooks,把所有类型的钩子都run()起来。
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) {} }}
java.lang.ApplicationShutdownHooks
用户等级的关闭钩子由ApplicationShutdownHooks来存放和维护。
在ApplicationShutdownHooks中,通过一个Map属性hooks来保存用户的关闭钩子。实际上每个关闭钩子都是一个线程,当JVM关闭事件触发的时候,就会执行钩子逻辑。
private staticIdentityHashMap<Thread, Thread> hooks;
这里使用IdentityHashMap来储存,这个map的key只比较地址,确保钩子的唯一性。即使两个对象属性完全一样,这里也会当成两个hook来处理。这个IdentityHashMap的具体实现,可以参考我的《JDK-Map源码浅析——IdentityHashMap》。
初始化,添加钩子类型
在ApplicationShutdownHooks加载的时候,首先会往Shutdown中add一个应用类型(1)的钩子类型线程,然后把 执行钩子逻辑的runHooks() 放入run()中等待调用。
这里ApplicationShutdownHooks为什么不实现Runnable接口,然后直接把自己add到Shutdown中呢? 因为没有必要:
因为Shutdown只关心用户的钩子,而对维护者ApplicationShutdownHooks没有兴趣,基于安全或者使用来考虑,都没有必要把ApplicationShutdownHooks整个传进去。所以这里新建一个线程,只把 runHooks()的调用 放进去,足矣。
这样一来,ApplicationShutdownHooks就不用实例化,可以做成单例,彻底变成了一个工具类,只用适度地暴露一些工具方法即可,并且通过静态属性hooks来保存钩子。
让ApplicationShutdownHooks和钩子类型线程分离,使职能独立化简单化。
static { try { Shutdown.add(1, false, new Runnable() { public void run() { runHooks(); } } ); hooks = new IdentityHashMap<>(); } catch (IllegalStateException e) { hooks = null; } } private ApplicationShutdownHooks() {}
添加/删除钩子,需要进行一些有效校验。
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; }
运行钩子
运行钩子方法runHooks(),提供给钩子类型线程调用。
定义一个局部Thread来转移hooks,转移后清空hooks。能看出来,关闭钩子逻辑只会执行一次,执行后就清空了,因为JVM关闭才会触发。另外,转移和清空也有利于避免并发的冲突。
接着启动这些钩子线程,而且通过join来保证线程的同步执行。(此处恰到好处地把start和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) { } } }
—— 源码来自于 JDK1.7
此处给大家留两个问题,大伙可以留言交流交流想法和心得
1、Shutdown.hooks为什么要用Runnable数组,而不用Thread数组呢?
2、文章最后的方法ApplicationShutdownHooks.runHooks()为什么要分别for循环Thread.start()和Thread.join()呢?为什么不把两个方法放到同一个for循环?分别有什么效果?
- JVM关闭钩子(2)—— 源码浅析
- JVM关闭钩子(1) —— 概念和应用
- 初探Tomcat源码 —— 关闭钩子
- 随记:安全地关闭 jvm(tomcat停止钩子事件处理)
- 深入JVM关闭与关闭钩子
- JVM里注册Spring的关闭钩子
- 关闭钩子(ShutdownHook)
- 从JDK源码看关闭钩子
- 从JDK源码看关闭钩子
- Flask源码阅读(八)——请求钩子
- 如何为JVM添加关闭钩子与简要分析
- How tomcat works——16 关闭钩子
- 关闭钩子
- JVM——Java虚拟机的浅析
- java的关闭钩子(Shutdown Hook)
- JDK8—LinkedHashMap源码浅析
- 消息钩子注册浅析
- 钩子初步——鼠标钩子程序
- 数据结构中排序算法总结(C++版)
- VMware虚拟机XP系统安装教程
- 在Github上重命名仓库
- Generate Parentheses
- ibus开机启动
- JVM关闭钩子(2)—— 源码浅析
- 用DOS命令,获取一个目录下的文件数目
- Sqlserver2008 R2下配置DTS功能
- CF 145C: Lucky Subsequence
- 如何阅读一本书——阅读笔记+个人体会【建议写书评或文献综述前读读这本书】
- C程序设计课程-2012电子信息 第十一堂课后作业
- TX2440 看手册学习2440-NANDFLASH时序理解(ADS1.2编译)
- Windows8 安装Vs2008 报错的解决方法
- mfc 写的画折线函数