一、前言我们在升级系统的时候,经常需要先关闭自己的应用程序,然后再重启。在关闭应用之前,我们希望做一些前置操作,比如关闭数据库、redis连接、清理zookeeper的临时节点、释放分布式锁、持久化缓存数据等等。二、Linux的信号机制在Linux上,我们主要使用kill方法来关闭进程。执行完这条命令后,linux会向进程发送一个信号,进程收到后就可以做一些清理工作了。kill命令的默认信号值为15,也就是SIGTERM信号。通过kill-l查看linux支持哪些信号:linux提供了signal()api,可以注册信号处理函数:#include#include#include#include#includestaticvoidgracefulClose(intsig){printf("执行清理\n");printf("JVM关闭\n");exit(0);//正常关机}intmain(intargc,char*argv[]){if(signal(SIGTERM,gracefulClose)==SIG_ERR)exit(-1);printf("JVM已经启动\n");while(true){//executeworksleep(1);}三、Java提供的ShutdownHookJava不支持类似linux的信号机制,而是提供了Runtime.addShutdownHook(Threadhook)的api。在JVM关闭之前,每个Hook线程都会并发执行。publicclassShutdownHook{publicstaticvoidmain(String[]args)throwsInterruptedException{Runtime.getRuntime().addShutdownHook(newDbShutdownWork());System.out.println("JVM已经启动");while(true){Thread.sleep(10L);}}staticclassDbShutdownWorkextendsThread{publicvoidrun(){System.out.println("Closethedatabaseconnection");}}}四、SpringBoot提供的优雅关机功能我们一般使用如下方法启动一个Springboot应用:publicstaticvoidmain(String[]args)throwsException{SpringApplication.run(SampleController.class,args);}SpringApplication.run()代码如下,会调用refreshContext(context)方法:publicConfigurableApplicationContextrun(String...args){StopWatchstopWatch=newStopWatch();秒表。start();ConfigurableApplicationContextcontext=null;FailureAnalyzersanalyzers=null;configureHeadlessProperty();SpringApplicationRunListenerslisteners=getRunListeners(args);listeners.started();try{ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(args);(ConfigurableEnvironment=preenvironment,applicationArguments);BannerprintedBanner=printBanner(environment);context=createApplicationContext();analyzers=newFailureAnalyzers(context);prepareContext(context,environment,listeners,applicationArguments,printedBanner);refreshContext(context);afterRefresh(context,applicationArguments);监听器。完成(上下文,空);stopWatch.stop();如果(this.logStartupInfo){newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),stopWatch);}returncontext;}catch(Throwableex){handleRunFailure(上下文,监听器,analyzers,ex);thrownewIllegalStateException(ex);}}refreshContext()方法比较简单:privatevoidrefreshContext(ConfigurableApplicationContextcontext){refresh(context);//调用ApplicationContext.refresh()if(this.registerShutdownHook){//registerShutdownHook默认值为truetry{context.registerShutdownHook();}catch(AccessControlExceptionex){//Notallowedinsomeenvironments.}}}AbstractApplicationContext.registerShutdownHook()代码:publicvoidregisterShutdownHook(){if(this.shutdownHook==null){this.shutdownHook=newThread(){@Overridepublicvoidrun(){synchronized(startupShutdownMonitor){doClose();}}};Runtime.getRuntime().addShutdownHook(this.shutdownHook);}}非常显然,Springboot在启动时向JVM注册了一个ShutdownHook,从而实现在JVM关闭之前,Spring容器的正常关闭,而当Spring销毁时,会依次调用bean的destroy动作进行销毁.5、Dubbo优雅的关闭策略Dubbo也是基于ShutdownHook实现的。AbstractConfig静态代码:static{Runtime.getRuntime().addShutdownHook(newThread(newRunnable(){publicvoidrun(){if(logger.isInfoEnabled()){logger.info("Runshutdownhooknow.");}ProtocolConfig.destroyAll();}},"DubboShutdownHook"));}6.总结只要我们的应用程序运行在linux平台上,所有的优雅关机方案都是基于linux提供的信号机制提供的,JVM也是如此。Java并没有给我们提供对应其中一个的api。而是提供了ShutdownHook机制,也可以达到类似的效果。缺点是我们无法知道JVM关闭的原因。dubbo、springboot等成熟的开源框架已经实现了自动注册ShutdownHook的功能,避免用户忘记调用优雅关机API带来的问题,降低框架的使用难度。