ShutdownHook
来源:互联网 发布:mac qq多开 编辑:程序博客网 时间:2024/04/29 20:29
- 一什么是ShutdownHook
- Runtimejava中相关方法源码
- ApplicationShutdownHooksjava
- 二java进程平滑退出的意义
- 三java进程平滑退出的思路
- 四如何屏敝第三方组件的ShutdownHook
- 五实现服务的平滑退出
- 1 Http请求
- 2 dubbo请求
- 3 RabbitMQ消费
- 4 Quartz任务
- 六为何重启时有时会有ClassNotFoundException
- 一什么是ShutdownHook
一、什么是ShutdownHook?
在Java程序中可以通过添加关闭钩子,实现在程序退出时关闭资源、平滑退出的功能。
使用Runtime.addShutdownHook(Thread hook)方法,可以注册一个JVM关闭的钩子,这个钩子可以在以下几种场景被调用:
1. 程序正常退出
2. 使用System.exit()
3. 终端使用Ctrl+C触发的中断
4. 系统关闭
5. 使用Kill pid命令干掉进程
Runtime.java中相关方法源码
public void addShutdownHook(Thread hook) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("shutdownHooks")); } ApplicationShutdownHooks.add(hook);}public boolean removeShutdownHook(Thread hook) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("shutdownHooks")); } return ApplicationShutdownHooks.remove(hook);}
ApplicationShutdownHooks.java
class ApplicationShutdownHooks { /* The set of registered hooks */ private static IdentityHashMap<Thread, Thread> hooks; static { try { Shutdown.add(1 /* shutdown hook invocation order */, false /* not registered if shutdown in progress */, new Runnable() { public void run() { runHooks(); } } ); hooks = new IdentityHashMap<>(); } catch (IllegalStateException e) { // application shutdown hooks cannot be added if // shutdown is in progress. hooks = null; } } private ApplicationShutdownHooks() {} /* Add a new shutdown hook. Checks the shutdown state and the hook itself, * but does not do any security checks. */ static synchronized void add(Thread hook) { if(hooks == null) throw new IllegalStateException("Shutdown in progress"); if (hook.isAlive()) throw new IllegalArgumentException("Hook already running"); if (hooks.containsKey(hook)) throw new IllegalArgumentException("Hook previously registered"); hooks.put(hook, hook); } /* Remove a previously-registered hook. Like the add method, this method * does not do any security checks. */ static synchronized boolean remove(Thread hook) { if(hooks == null) throw new IllegalStateException("Shutdown in progress"); if (hook == null) throw new NullPointerException(); return hooks.remove(hook) != null; } /* Iterates over all application hooks creating a new thread for each * to run in. Hooks are run concurrently and this method waits for * them to finish. */ static void runHooks() { Collection<Thread> threads; synchronized(ApplicationShutdownHooks.class) { threads = hooks.keySet(); hooks = null; } for (Thread hook : threads) { hook.start(); } for (Thread hook : threads) { try { hook.join(); } catch (InterruptedException x) { } } }}
二、java进程平滑退出的意义
很多时候,我们会有这样的一些场景,比如说nginx反向代理若干个负载均衡的web容器,又或者微服务架构中存在的若干个服务节点,需要进行无间断的升级发布。
在重启服务的时候,除非我们去变更nginx的配置,否则重启很可能会导致正在执行的线程突然中断,本来应该要完成的事情只完成了一半,并且调用方出现错误警告。
如果能有一种简单的方式,能够让进程在退出时能执行完当前正在执行的任务,并且让服务的调用方将新的请求定向到其他负载节点,这将会很有意义。
自己注册ShutdownHook可以帮助我们实现java进程的平滑退出。
三、java进程平滑退出的思路
- 在服务启动时注册自己的ShutdownHook
- ShutdownHook在被运行时,首先不接收新的请求,或者告诉调用方重定向到其他节点
- 等待当前的执行线程运行完毕,如果五秒后仍在运行,则强制退出
四、如何屏敝第三方组件的ShutdownHook
我们会发现,有一些第三方组件在代码中注册了关闭自身资源的ShutdownHook,这些ShutdownHook对于我们的平滑退出有时候起了反作用。
比如dubbo,在static方法块里面注册了自己的关闭钩子,完全不可控。在进程退出时直接就把长连接给断开了,导致当前的执行线程无法正常完成,源码如下:
static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { if (logger.isInfoEnabled()) { logger.info("Run shutdown hook now."); } ProtocolConfig.destroyAll(); } }, "DubboShutdownHook"));}
从Runtime.java和ApplicationShutdownHooks.java的源码中,我们看到并没有一个可以遍历操作shutdownHook的方法。
Runtime.java仅有的一个removeShutdownHook的方法,对于未写线程名的匿名类来说,无法获取对象的引用,也无法分辨出彼此。
ApplicationShutdownHooks.java不是public的,类中的hooks也是private的。
只有通过反射的方式才能获取并控制它们。定义ExcludeIdentityHashMap类来帮助我们阻止非自己的ShutdownHook注入
class ExcludeIdentityHashMap<K,V> extends IdentityHashMap<K,V> { public V put(K key, V value) { if (key instanceof Thread) { Thread thread = (Thread) key; if (!thread.getName().startsWith("My-")) { return value; } } return super.put(key, value); }}
通过反射的方式注入自己的ShutdownHook并清除其他Thread
String className = "java.lang.ApplicationShutdownHooks";Class<?> clazz = Class.forName(className);Field field = clazz.getDeclaredField("hooks");field.setAccessible(true);Thread shutdownThread = new Thread(new Runnable() { @Override public void run() { // TODO }});shutdownThread.setName("My-WebShutdownThread");IdentityHashMap<Thread, Thread> excludeIdentityHashMap = new ExcludeIdentityHashMap<>();excludeIdentityHashMap.put(shutdownThread, shutdownThread);synchronized (clazz) { IdentityHashMap<Thread, Thread> map = (IdentityHashMap<Thread, Thread>) field.get(clazz); for (Thread thread : map.keySet()) { Log.info("found shutdownHook: " + thread.getName()); excludeIdentityHashMap.put(thread, thread); } field.set(clazz, excludeIdentityHashMap);}
五、实现服务的平滑退出
对于一般的微服务来说,有这几种任务的入口:Http请求、dubbo请求、RabbitMQ消费、Quartz任务
5.1 Http请求
测试发现Jetty容器在stop的时候不能实现平滑退出,springboot默认使用的tomcat容器可以,以下是部分代码示例:
EmbeddedWebApplicationContext embeddedWebApplicationContext = (EmbeddedWebApplicationContext) applicationContext;EmbeddedServletContainer embeddedServletContainer = embeddedWebApplicationContext.getEmbeddedServletContainer();if (embeddedServletContainer instanceof TomcatEmbeddedServletContainer) { Connector[] connectors = tomcatEmbeddedServletContainer.getTomcat().getService().findConnectors(); for (Connector connector : connectors) { connector.pause(); } for (Connector connector : connectors) { Executor executor = connector.getProtocolHandler().getExecutor(); if (executor instanceof ThreadPoolExecutor) { try { ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor; threadPoolExecutor.shutdown(); if (!threadPoolExecutor.awaitTermination(5, TimeUnit.SECONDS)) { log.warn("Tomcat thread pool did not shutdown gracefully within 5 seconds. Proceeding with forceful shutdown"); } } catch (InterruptedException e) { log.warn("TomcatShutdownHook interrupted", e); } } }}
5.2 dubbo请求
尝试了许多次,看了相关的源码,dubbo不支持平滑退出;解决方法只有一个,那就是修改dubbo的源码,以下两个地址有详细介绍:
http://frankfan915.iteye.com/blog/2254097
https://my.oschina.net/u/1398931/blog/790709
5.3 RabbitMQ消费
以下是SpringBoot的示例,不使用Spring原理也是一样的
RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry = applicationContext.getBean( RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, RabbitListenerEndpointRegistry.class);Collection<MessageListenerContainer> containers = rabbitListenerEndpointRegistry.getListenerContainers();for (MessageListenerContainer messageListenerContainer : containers) { messageListenerContainer.stop();}
5.4 Quartz任务
quartz也比较简单
Scheduler scheduler = applicationContext.getBean(Scheduler.class);scheduler.shutdown(true);
六、为何重启时有时会有ClassNotFoundException
springboot通过java -jar example.jar的方式启动项目,在使用脚本restart的时候,首先覆盖旧的jar包,然后stop旧线程,启动新线程,这样就可能会出现此问题。因为在stop的时候,ShutdownHook线程被唤醒,在其执行过程中,某些类(尤其是匿名类)还未加载,这时候就会通知ClassLoader去加载;ClassLoader持有的是旧jar包的文件句柄,虽然新旧jar包的名字路径完全一样,但是ClassLoader仍然是使用open着的旧jar包文件,文件已经找不到了,所以类加载不了就ClassNotFound了。
如何解决呢?也许有更优雅的方式,但是我没有找到;但是我们可以简单地把顺序调整一下,先stop、再copy覆盖、最后start,这样就OK了。
- ShutdownHook
- ShutdownHook
- ShutdownHook
- shutdownHook
- ShutdownHook
- 关闭钩子(ShutdownHook)
- Runtime.getRuntime().addShutdownHook(shutdownHook);
- Runtime.getRuntime().addShutdownHook(shutdownHook);
- java ShutdownHook(钩子线程)
- java jvm shutdownhook
- Runtime.getRuntime().addShutdownHook(shutdownHook);
- 利用ShutdownHook释放系统资源
- Runtime ApplicationShutdownHooks , Shutdownhook
- Java利用ShutDownHook关闭系统资源
- Java利用ShutDownHook关闭系统资源
- 为程序加上“关闭钩子”(ShutdownHook)
- 黑马程序员——Java中的ShutdownHook
- Ehcache缓存(三)ClassLoaderUtil及shutdownhook
- 淘淘商城系列——发布dubbo服务
- linux pppd-GPRS和以太网同时在线,解决拨号默认网关
- Python(5) Image和Ndarray互相转换
- crontab
- linux-linux常用命令汇总
- ShutdownHook
- Cardboard的一些坑
- 聊聊并发(六)ConcurrentLinkedQueue的实现原理分析
- js 定时器
- [数据结构]表达式的计算与转换-C++
- Physics-based Animation 学习总结
- #!/usr/bin/env python与#!/usr/bin/python的区别
- 设计模式学习笔记(Java篇)之简单工厂模式
- net start mysql报发生系统错误2 -- 找不到文件路径