什么是关闭钩子(Shutdown Hook)?先看看JavaDoc的说明:java
关闭钩子是指经过Runtime.addShutdownHook注册的但还没有开始的线程。这些钩子能够用于实现服务或者应用程序的清理工做,例如删除临时文件,或者清除没法由操做系统自动清除的资源。数据库
JVM既能够正常关闭,也能够强行关闭。正常关闭的触发方式有多种,包括:当最后一个“正常(非守护)”线程结束时,或者当调用了System.exit时,或者经过其余特定于平台的方法关闭时(例如发送了SIGINT信号或者键入Ctrl-C)。安全
在正常关闭中,JVM首先调用全部已注册的关闭钩子。JVM并不能保证关闭钩子的调用顺序。在关闭应用程序线程时,若是有(守护或者非守护)线程仍然在执行,那么这些线程接下来将与关闭进程并发执行。当全部的关闭钩子都执行结束时,若是runFinalizersOnExit为true【经过Runtime.runFinalizersOnExit(true)设置】,那么JVM将运行这些Finalizer(对象重写的finalize方法),而后再中止。JVM不会中止或中断任何在关闭时仍然运行的应用程序线程。当JVM最终结束时,这些线程将被强行结束。若是关闭钩子或者Finalizer没有执行完成,那么正常关闭进程“挂起”而且JVM必须被强行关闭。当JVM被强行关闭时,只是关闭JVM,并不会运行关闭钩子。并发
在编写关闭钩子时,须要注意如下几点:函数
下面是一个简单的示例:this
public class T { @SuppressWarnings("deprecation") public static void main(String[] args) throws Exception { //启用退出JVM时执行Finalizer Runtime.runFinalizersOnExit(true); MyHook hook1 = new MyHook("Hook1"); MyHook hook2 = new MyHook("Hook2"); MyHook hook3 = new MyHook("Hook3"); //注册关闭钩子 Runtime.getRuntime().addShutdownHook(hook1); Runtime.getRuntime().addShutdownHook(hook2); Runtime.getRuntime().addShutdownHook(hook3); //移除关闭钩子 Runtime.getRuntime().removeShutdownHook(hook3); //Main线程将在执行这句以后退出 System.out.println("Main Thread Ends."); } } class MyHook extends Thread { private String name; public MyHook (String name) { this.name = name; setName(name); } public void run() { System.out.println(name + " Ends."); } //重写Finalizer,将在关闭钩子后调用 protected void finalize() throws Throwable { System.out.println(name + " Finalize."); } }
和(可能的)执行结果(由于JVM不保证关闭钩子的调用顺序,所以结果中的第2、三行可能出现相反的顺序):spa
Main Thread Ends. Hook2 Ends. Hook1 Ends. Hook3 Finalize. Hook2 Finalize. Hook1 Finalize.
能够看到,main函数执行完成,首先输出的是Main Thread Ends,接下来执行关闭钩子,输出Hook2 Ends和Hook1 Ends。这两行也能够证明:JVM确实不是以注册的顺序来调用关闭钩子的。而因为hook3在调用了addShutdownHook后,接着对其调用了removeShutdownHook将其移除,因而hook3在JVM退出时没有执行,所以没有输出Hook3 Ends。操作系统
另外,因为MyHook类实现了finalize方法,而main函数中第一行又经过Runtime.runFinalizersOnExit(true)打开了退出JVM时执行Finalizer的开关,因而3个hook对象的finalize方法被调用,输出了3行Finalize。线程
注意,屡次调用addShutdownHook来注册同一个关闭钩子将会抛出IllegalArgumentException:日志
Exception in thread "main" java.lang.IllegalArgumentException: Hook previously registered at java.lang.ApplicationShutdownHooks.add(ApplicationShutdownHooks.java:72) at java.lang.Runtime.addShutdownHook(Runtime.java:211) at T.main(T.java:12)
另外,从JavaDoc中得知:
Once the shutdown sequence has begun it can be stopped only by invoking the halt method, which forcibly terminates the virtual machine.
Once the shutdown sequence has begun it is impossible to register a new shutdown hook or de-register a previously-registered hook. Attempting either of these operations will cause an IllegalStateException to be thrown.
“一旦JVM关闭流程开始,就只能经过调用halt方法来中止该流程,也不可能再注册或移除关闭钩子了,这些操做将致使抛出IllegalStateException”。
若是在关闭钩子中关闭应用程序的公共的组件,如日志服务,或者数据库链接等,像下面这样:
Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { try { LogService.this.stop(); } catch (InterruptedException ignored){ //ignored } } });
因为关闭钩子将并发执行,所以在关闭日志时可能致使其余须要日志服务的关闭钩子产生问题。为了不这种状况,可使关闭钩子不依赖那些可能被应用程序或其余关闭钩子关闭的服务。实现这种功能的一种方式是对全部服务使用同一个关闭钩子(而不是每一个服务使用一个不一样的关闭钩子),而且在该关闭钩子中执行一系列的关闭操做。这确保了关闭操做在单个线程中串行执行,从而避免了在关闭操做以前出现竞态条件或死锁等问题。