ShutdownHook

来源:互联网 发布:mac qq多开 编辑:程序博客网 时间:2024/04/29 20:29

    • 一什么是ShutdownHook
        • Runtimejava中相关方法源码
        • ApplicationShutdownHooksjava
    • 二java进程平滑退出的意义
    • 三java进程平滑退出的思路
    • 四如何屏敝第三方组件的ShutdownHook
    • 五实现服务的平滑退出
      • 1 Http请求
      • 2 dubbo请求
      • 3 RabbitMQ消费
      • 4 Quartz任务
    • 六为何重启时有时会有ClassNotFoundException

一、什么是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进程平滑退出的思路

  1. 在服务启动时注册自己的ShutdownHook
  2. ShutdownHook在被运行时,首先不接收新的请求,或者告诉调用方重定向到其他节点
  3. 等待当前的执行线程运行完毕,如果五秒后仍在运行,则强制退出

四、如何屏敝第三方组件的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了。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 中文字幕边打电话边做 白石茉利奈star-782 在丈夫面前被别人犯侵天翼电影 中文版丈夫的上司连续浸七天 吉尺明步110部连接 吉g明教师步中文字迅雷下载 吉次明涉作品 被丈夫好友侵犯有感觉了中文3 侵犯新娘在丈夫面前被侵先锋 吉朋步高清在线 吉译朋步查官 新娘在婚礼上的厕所被动态 在丈夫遗像面前被侵演员叫什么 吉译朋步30分钟视频教程 吉译朋步义兄 吉译朋步孩子线播放 丈夫面前侵犯中文高清在线播放 在线中文字幕最好看最经典 丈夫面前不允许出声的侵犯2 2018吉译明i作品全集一览 好看的中文字幕推荐 yellow字幕幕网 中文字幕人人视频在线萝莉 中文永久字幕在线播放 好看的字幕最新字幕一经典幕 母乳妻诱惑中文字幕 有什么好看的电影 2018好看的大陆电影 什么动漫电影好看中文 在线播放丝服制袜 2018最新手机中文字幕 中文字幕视频2018 中文字幕爸爸在线播放 濡x母 もう、やめて… 中文字幕在线手机播放2018 免费中文字幕 啄木鸟中文字幕在线播放军舰 中文字幕永久在线2016 免费伦电影中文字幕 大嫂潮湿中文字幕在线迅雷 小白看看永久播放平台