Runtime#addShutDownHook方法是给虚拟机增加一个虚拟机关闭时的调用钩子,在虚拟机关闭的时候调用这些钩子线程。还是非常有用的一个方法,最直接的用法就是监控了,因为其是在虚拟机临关闭时被调用,所以天生可以记录虚拟机关闭这件事情,及其相关的信息;再就是清理资源什么的,也可以做一个钩子线程,这样就不用再应用中为这些清理资源的操作找合适的位置了;
下面先翻译一下这个方法的Java doc,在网上找到几篇翻译,都出自一个版本,错误挺多的,这里重新翻译一下:
doc的第一句话是Registers a new virtual-machine shutdown hook.网上的翻译的版本将其翻译成了注册一个新的虚拟机来关闭钩子,这样翻译从语法的角度来讲就是不对的嘛,一句话里Register和Shutdown都成了动词。很简单的一句话,就是注册一个新的虚拟机关闭钩子。可以用下面的代码验证一下,主程序和这些钩子线程是不是运行在一个JVM中:
public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new Thread(){ @Override public void run() { System.out.println("I am still alive!"); try { Thread.currentThread().sleep(1000); System.out.println(Runtime.getRuntime().toString()); System.out.println(Runtime.getRuntime().hashCode()); } catch (InterruptedException e) { e.printStackTrace(); } } }); try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Runtime.getRuntime().toString()); System.out.println(Runtime.getRuntime().hashCode()); System.out.println("I am exit!"); }
主程序中打印出的Runtime的对象的信息和钩子线程中打印出的Runtime的信息是一致的。
下面开始正式的翻译:
Runtime#addShutDownHook方法会注册一个新的 虚拟机关闭钩子;
Java虚拟机只在响应下面两种事件时关闭:
1.当最后的非守护进程结束时,程序正常结束;或者当exit方法(统称,比如System.exit()方法)被调用时
2.JVM响应用户的中断操作,例如键入了^C或者一个系统级的事件,例如用户退出登录或者系统(JVM所在的系统,比如PC的操作系统,Android)关闭。
一个关闭钩子是一个简单初始化还没有启动的线程。当虚拟机开始进入关闭阶段时,虚拟机将以不确定的顺序启动所有已经注册的关闭钩子,当所有的钩子线程结束时,如果finalization-on-exit 启用,JVM将接着运行所有还没被调用的finalizer。最后,虚拟机停止。注意,在JVM的关闭阶段,守护线程将持续运行,如果JVM的关闭阶段是通过调用exit方法进入的,非守护的线程也会在JVM的关闭阶段持续运行。
一旦进入关闭工序,JVM将只能通过调用halt方法停止,这个方法将强制结束虚拟机。
一旦关闭工序开始,虚拟机不能再被注册新的ShutDown hook,也不能撤销之前注册的钩子。这两个操作都会导致抛出一个IllegalStateException错误。
ShutDown钩子运行在Java虚拟机整个生命周期的一个很微妙的时刻,虚拟机生命周期的最后阶段,因此在编写程序时应该十分小心。这些钩子线程应该都是线程安全的,并且要避免任何可能的死锁的发生。钩子线程不应该依赖于绑定的服务,这些绑定的服务包括,注册到虚拟机的其他的钩子线程和在JVM已经开启关闭工序时钩子线程自己。使用其他基于线程的服务,像AWT的事件分发线程,可能导致死锁。
关闭钩子线程中未捕获的错误的处理方式跟其他的线程一样,通过调用ThreadGroup#uncaughtException方法,这个方法的默认实现是打印错误堆栈信息(System#err),然后结束线程;这个方法不会导致虚拟机结束或终止。
在罕见的情况下,虚拟机可能会abort,也就是说,没有干净的关闭而直接停止运行。这种情况会发生在当虚拟机被外部的力量结束时。例如,Unix中的SIGKILL信号或者window平台中的TerminateProcess被调用。虚拟机也可能在一个native的方法出错时abort,例如,毁坏的内部数据结构或者尝试访问不存在的内存。如果虚拟机abort,将不能保证关闭钩子线程被完整运行。
上边这一段是Runtime#addShutDownHook方法的注释。简单总结一下,钩子线程的启动时机:
1.虚拟机自己结束时会调用,比如程序运行完成,或者用户跟虚拟机交互之后,虚拟机接收到退出信号,自己结束
2.外部力量结束虚拟机时,不能保证钩子线程启动,比如在windows平台中,启动任务管理器,直接将这个Java虚拟机关闭进程,这种情况下不能保证钩子线程一定会被调用
上边的1和2都可以通过稍微修改下最上边的程序验证。