安全釋出之Jvm友好關閉

weixin_33724059發表於2017-11-30

小夥伴們一定很熟悉kill -9 來殺某個程式了,其實這樣做在生產系統中是存在安全隱患的。

我們知道執行緒分為守護執行緒和使用者執行緒。如果是系統中存在的均是守護執行緒,那麼呼叫JVM的關閉講課以關閉否則(存在至少一個使用者執行緒)是無法正常關閉的。

因此程式碼能否響應中斷是一個很重要的標誌。

我們熟悉的Java的GC執行緒

133016_Bq7z_871390.png

就是一個很典型的守護執行緒。

各位在關閉tomcat時,在使用到執行緒池時經常會碰到呼叫tomcat的關閉指令碼無法正常關閉就是因為系統中存在一些非守護執行緒導致無法正常退出導致。

那麼各位可能想了,我將所有的執行緒池起的執行緒都設定為守護執行緒那麼在呼叫shutdown的時候是否時就會正常關閉呢?

thread.setDaemon(true);

答案是肯定的,但是帶來一個其他的問題===》如果此時程式碼只執行了一半那麼怎麼處理呢?比如你消費了一個jms的訊息,但是沒有消費完成,然後‘砰’所有的執行緒灰飛煙滅,恰巧這個訊息裡面是個1000w的大單……

    嚴重: The web application [] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered. 
    2013-1-8 15:02:53 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads 
    嚴重: The web application [] appears to have started a thread named [Thread-2] but has failed to stop it. This is very likely to create a memory leak. 
    2013-1-8 15:02:53 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads 
    嚴重: The web application [] appears to have started a thread named [thread-snatch-picture] but has failed to stop it. This is very likely to create a memory leak. 
    2013-1-8 15:02:53 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads 
    嚴重: The web application [] appears to have started a thread named [Xmemcached-Reactor-0] but has failed to stop it. This is very likely to create a memory leak. 
    2013-1-8 15:02:53 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads 
    嚴重: The web application [] appears to have started a thread named [Xmemcached-Reactor-1] but has failed to stop it. This is very likely to create a memory leak. 
    2013-1-8 15:02:53 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads 
    嚴重: The web application [] appears to have started a thread named [Xmemcached-Reactor-2] but has failed to stop it. This is very likely to create a memory leak. 
    2013-1-8 15:02:53 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads 
    嚴重: The web application [] appears to have started a thread named [Xmemcached-Reactor-3] but has failed to stop it. This is very likely to create a memory leak.

通常我們呼叫tomcat關閉指令碼如果沒有關閉那麼log重可能吐出類似下面的日誌

某些小夥伴還可能看到如下的log

    Exception in thread "Xmemcached-Reactor-3" java.lang.NoClassDefFoundError: org/apache/log4j/spi/ThrowableInformation
     at org.apache.log4j.spi.LoggingEvent.(LoggingEvent.java:159)
     at org.apache.log4j.Category.forcedLog(Category.java:391)
     at org.apache.log4j.Category.log(Category.java:856)
     at org.slf4j.impl.Log4jLoggerAdapter.info(Log4jLoggerAdapter.java:382)
     at com.captaindebug.longpoll.service.DeferredResultService.run(DeferredResultService.java:75)
     at java.lang.Thread.run(Thread.java:722)
    Caused by: java.lang.ClassNotFoundException: org.apache.log4j.spi.ThrowableInformation
     at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1714)
     at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1559)
     ... 6 more

上述的原因是由於出現類解除安裝之後使用了一些已經不存在類載入器的類導致報錯

小夥伴可能覺得太Easy了,怎麼能難道有一定經驗的開發小夥伴呢?

kill -9 `ps -ef|grep <tomcat>|awk '{print $2}'`

當然下次啟動時沒有問題了,但是可能就導致某些消費沒有完成。

本篇重點來了 ShutDownHook 顧名思義這是在Jvm關閉時的鉤子函式

“一個shutdown hook就是一個初始化但沒有啟動的執行緒。 當虛擬機器開始執行關閉程式時,它會啟動所有已註冊的shutdown hook(不按先後順序)並且併發執行。”

比如我們檢視一下tomcat的shutdownhook

    /**
     * Start a new server instance.
     */
    public void start() {
     
        if (getServer() == null) {
            load();
        }
     
        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }
     
        long t1 = System.nanoTime();
     
        // Start the new server
        try {
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }
     
        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
        }
     
        // Register shutdown hook
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);
     
            // If JULI is being used, disable JULI's shutdown hook since
            // shutdown hooks run in parallel and log messages may be lost
            // if JULI's hook completes before the CatalinaShutdownHook()
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }
     
        if (await) {
            await();
            stop();
        }
    }

再比如Dubbo中各個元件中

    static {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            public void run() {
                if (logger.isInfoEnabled()) {
                    logger.info("Run shutdown hook now.");
                }
                ProtocolConfig.destroyAll();
            }
        }, "DubboShutdownHook"));
    }

我們可以在對應使用清理Hook

相關文章