通过JDK源码分析关闭钩子详解
关闭钩子
用户关闭关闭程序,需要做一些善后的清理工作,但问题是,某些用户不会按照推荐的方法关闭应用程序,肯能导致善后工作无法进行。像tomcat调用server的start方法启动容器,然后会逐级调用start。当发出关闭命令是会启动关闭功能,但是关闭可能会有一些意外产生,导致应用程序没有进入到我们制定的关闭方法去。如何解决这个问题呢,使得即使有意外也能正常进入关闭流程。
好在java提供了一种优雅的方式去解决这种问题。使得关闭的善后处理的代码能执行。java的关闭钩子能确保总是执行,无论用户如何终止应用程序。除非用户kill,这个是个死穴。
Java提供了ShutdownHook机制,它让我们在程序正常退出或者发生异常时能有机会做一些清场工作。使用的方法也很简单,Java.Runtime.addShutdownHook(Threadhook)即可。关闭钩子其实可以看成是一个已经初始化了的但还没启动的线程,当JVM关闭时会并发地执行注册的所有关闭钩子。
对java而言,虚拟机会对以下几种操作进行关闭:
(1)系统调用System.exit()方法
(2)程序最后一个守护线程退出时,应用程序正常退出。
(3)用户强行中断程序运行,比如ctrl+c等其他方式中断java程序
关闭钩子的生成:
1.创建Thread的子类
2.实现run方法,应用程序关闭时会调用该方法,不需要调用start方法
3.在应用中实例化关闭钩子类
4.使用Runtime注册关闭钩子
钩子执行时机
向JVM注册关闭钩子后的什么时候会被调用,什么时候不会被调用呢?分成以下情况:
- Java程序正常运行完退出时会被调用。
- windows和linux终端中通过ctrl-c终止命令时会被调用。
- JVM发生OutOfMemory而退出时会被调用。
- Java程序中执行System.exit()时会被调用。
- 操作系统关闭时会被调用。
- linux通过killpid(除了kill-9pid)结束进程时会被调用。
- windows直接结束进程时不会被调用。
添加删除钩子
钩子的添加和删除都是通过Runtime来实现,里面的实现也比较简单,可以看到addShutdownHook和removeShutdownHook方法都是先通过安全管理器先检查是否有shutdownHooks的权限,然后再通过ApplicationShutdownHooks添加和删除钩子。
publicvoidaddShutdownHook(Threadhook){ SecurityManagersm=System.getSecurityManager(); if(sm!=null){ sm.checkPermission(newRuntimePermission("shutdownHooks")); } ApplicationShutdownHooks.add(hook); } publicbooleanremoveShutdownHook(Threadhook){ SecurityManagersm=System.getSecurityManager(); if(sm!=null){ sm.checkPermission(newRuntimePermission("shutdownHooks")); } returnApplicationShutdownHooks.remove(hook); }
ApplicationShutdownHooks保管钩子
ApplicationShutdownHooks可以看成是用来保管所有关闭钩子的容器,而主要是通过一个IdentityHashMap
privatestaticIdentityHashMaphooks;
有了hooks这个变量,添加删除钩子就是直接向这个HashMap进行put和remove操作了,其中在操作前也会做一些检查,比如添加钩子前会做三个判断:
1.所有钩子是否已经开始执行了,hooks为null即表示所有关闭钩子已经开始执行,此时不能再添加了。
2.钩子状态是否为alive,是则表示钩子已经在运行,不能添加了。
3.是否已经包含了该钩子,已包含则不能再添加。
类似的判断逻辑还有remove操作。
staticsynchronizedvoidadd(Threadhook){ if(hooks==null) thrownewIllegalStateException("Shutdowninprogress"); if(hook.isAlive()) thrownewIllegalArgumentException("Hookalreadyrunning"); if(hooks.containsKey(hook)) thrownewIllegalArgumentException("Hookpreviouslyregistered"); hooks.put(hook,hook); } staticsynchronizedbooleanremove(Threadhook){ if(hooks==null) thrownewIllegalStateException("Shutdowninprogress"); if(hook==null) thrownewNullPointerException(); returnhooks.remove(hook)!=null; }
而ApplicationShutdownHooks中真正负责启动所有钩子的任务由runHooks方法负责,它的逻辑如下:
1.先对ApplicationShutdownHooks类加锁并取到所有钩子,然后将hooks变量设为null。
2.遍历所有钩子,分别启动钩子,前面有说到关闭钩子其实可以看成是一个已经初始化了的但还没启动的线程,这里调用start方法将其启动即可。
3.用join方法协调所有钩子线程,等待他们执行完毕。
staticvoidrunHooks(){ Collectionthreads; synchronized(ApplicationShutdownHooks.class){ threads=hooks.keySet(); hooks=null; } for(Threadhook:threads){ hook.start(); } for(Threadhook:threads){ try{ hook.join(); }catch(InterruptedExceptionx){} } }
ApplicationShutdownHooks的runHooks方法又是由谁负责调用的呢?如下,它其实是变成一个Runnable对象添加到Shutdown类中了,Runnable的run方法负责调用runHooks方法。接下去就要看Shutdown类什么时候执行该Runnable对象了。
Shutdown.add(1,false, newRunnable(){ publicvoidrun(){ runHooks(); } } );
Shutdown中的钩子
ApplicationShutdownHooks的Runnable对象添加到Shutdown中的逻辑如下,
privatestaticfinalintRUNNING=0; privatestaticfinalintHOOKS=1; privatestaticfinalintFINALIZERS=2; privatestaticfinalintMAX_SYSTEM_HOOKS=10; privatestaticfinalRunnable[]hooks=newRunnable[MAX_SYSTEM_HOOKS]; staticvoidadd(intslot,booleanregisterShutdownInProgress,Runnablehook){ synchronized(lock){ if(hooks[slot]!=null) thrownewInternalError("Shutdownhookatslot"+slot+"alreadyregistered"); if(!registerShutdownInProgress){ if(state>RUNNING) thrownewIllegalStateException("Shutdowninprogress"); }else{ if(state>HOOKS||(state==HOOKS&&slot<=currentRunningHook)) thrownewIllegalStateException("Shutdowninprogress"); } hooks[slot]=hook; } }
slot表示将Runnable对象赋给hooks数组中的哪个元素中,Shutdown中同样有一个hooks变量,它是Runnable[]类型,长度为MAX_SYSTEM_HOOKS,即为10。这个数组可以看成是钩子的优先级实现,数组下标用于表示优先级,slot=1则表示赋值到数组中第二个元素。
registerShutdownInProgress表示是否允许注册钩子,即使正在执行shutdown。前面传入false,显然是不允许。其中state>RUNNING条件表示其他状态都要抛出异常,除非是RUNNING状态,这个很好理解,一共有三个状态,RUNNING、HOOKS、FINALIZERS,值分别为0、1、2。如果registerShutdownInProgress为true则只要不为FINALIZERS状态,同时slot也要大于当前钩子数组的下标即可。
在前面说到的钩子执行时机的情况下,JVM都会调用到Shutdown类的sequence方法,如下,
privatestaticvoidsequence(){ synchronized(lock){ if(state!=HOOKS)return; } runHooks(); booleanrfoe; synchronized(lock){ state=FINALIZERS; rfoe=runFinalizersOnExit; } if(rfoe)runAllFinalizers(); } privatestaticvoidrunHooks(){ for(inti=0;i首先判断当前状态不等于HOOKS则直接返回,接着执行runHooks方法,这个方法也是我们主要要看的方法。然后再将状态设为FINALIZERS,最后如果需要的话还要调用runAllFinalizers方法执行所有finalizer。所以在JVM关闭时runHooks方法是会被调用的。
runHooks方法逻辑简单,就是遍历Runnable数组,一个个调用其run方法让其执行。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。