平滑关闭的思路就是让正在执行的任务线程正常执行完毕,然后再关闭JVM。在JVM关闭之前触发一个shutdown hook,jvm自带这个hook,在java启动时候就可以注册这样的hook。
##1、简述JVM关闭钩子(shutdown hook) 首先JVM的关闭方式可以分为三种:
-
正常关闭:当最后一个非守护线程结束或者调用了System.exit或者通过其他特定平台的方法关闭(发送SIGINT,SIGTERM信号等)
-
强制关闭:通过调用Runtime.halt方法或者是在操作系统中直接kill(发送SIGKILL信号)掉JVM进程
-
异常关闭:运行中遇到RuntimeException异常等。
在某些情况下,我们需要在JVM关闭时做些扫尾的工作,比如删除临时文件、停止日志服务以及内存数据写到磁盘等,为此JVM提供了关闭钩子(shutdown hooks)来做这些事情。另外特别注意的是:如果JVM因异常关闭,那么子线程(Hook本质上也是子线程)将不会停止。但在JVM被强行关闭时,这些线程都会被强行结束。
另外在使用关闭钩子还要注意以下几点:
- 不能在钩子调用System.exit(),否则卡住JVM的关闭过程,但是可以调用Runtime.halt()。
- 不能再钩子中再进行钩子的添加和删掉操作,否则将会抛出IllegalStateException。
- 在System.exit()之后添加的钩子无效。
- 当JVM收到SIGTERM命令(比如操作系统在关闭时)后,如果钩子线程在一定时间没有完成,那么Hook线程可能在执行过程中被终止。
- Hook线程中同样会抛出异常,如果抛出异常又不处理,那么钩子的执行序列就会被停止。
##2、 ActiveMQ消费者的钩子
先讲一下我的消费者整体情况:
1、使用Spring集成ActiveMQ,用Spring容器进行bean的管理
2、用DefaultMessageListenerContainer来监听处理队列里的消息
直接贴代码:
//消费者是普通的java工程,通过这个类的main方法启动,这里只贴出main函数里的代码
public static void main( String[] args ) {log.info("start APP......");ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/applicationContext.xml");//testContainer是在配置文件中配置的监听器final DefaultMessageListenerContainer container = (DefaultMessageListenerContainer)context.getBean("testContainer");Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {public void run() {System.out.println("-------------------- 消费者JVM即将关闭,执行清场操作 --------------------");//关闭线程池,等待线程池积压消息处理container.shutdown();System.out.println("-------------------- 消费者关闭,线程池处理完毕 --------------------");//不要在钩子里面执行System.exit,调用halt()是可以正常关闭系统的,但是貌似没这个必要//Runtime.getRuntime().halt(0);}}));
}
为了可以更直观的看到这个等待任务线程处理完的一个过程,在任务线程里添加sleep代码。
public class TestMessageListener implements SessionAwareMessageListener {public void onMessage(Message message, Session session) {if (message instanceof TextMessage) {TextMessage textMsg = (TextMessage) message;try {Thread.sleep(1000L);System.out.println(Thread.currentThread().getName() + ",接收到一个纯文本消息,消息内容是:" + textMsg.getText());} catch (JMSException e) {e.printStackTrace();}catch (InterruptedException e) {e.printStackTrace();}}}
}
运行结果如下:
##3、 关闭消费者
关闭消费者的时候也很重要,不要使用kill -9的方式来杀进程,这是无脑杀。
ref:
- 深入JVM关闭与关闭钩子
- JAVA虚拟机关闭钩子(Shutdown Hook)
- Java消息队列任务的平滑关闭
- 使用后台进程和 Shutdown Hook 友好地关闭 Tomcat