本文首發於 vivo互聯網技術 微信公衆號
連接: https://mp.weixin.qq.com/s/ZqkmoAR4JEYr0x0Suoq7QQ
做者:馬運傑
本文經過閱讀Tomcat啓動和關閉流程的源碼,深刻分析不一樣的Tomcat關閉方式背後的原理,讓開發人員可以瞭解在使用不一樣的關閉方式時須要注意的點,避免因JVM進程異常退出致使的各類非預見性錯誤。編程
要了解Tomcat關閉的原理,首先須要關注下Tomcat是如何啓動的。這裏咱們簡單介紹下。安全
Tomcat啓動的入口是Bootstrap類中的main方法,然後根據server.xml中的配置,對Server、Service、Enigin、Connector、Host、Context等組件進行初始化,以後即是啓動這些組件。咱們重點來看下啓動以後,Tomcat作了哪些工做。服務器
在Tomcat的各組件啓動完畢以後,main主線程會進入Catalina.out的await()方法,而此方法又是主要調用了Server組件的await()方法,從名字即可以看出,這個方法的目的是爲了阻塞當前線程(main主線程)。微信
分析await的源碼(源碼比較長,這裏截取了部分,所有的能夠自行拉取Tomcat源碼進行閱讀)。運維
(StandardServer.await())socket
咱們發現await()方法主要是根據server.xml中Server節點port屬性的設置作了如下幾種工做:函數
在主線程退出等待後,就會進入Tomcat的關閉流程,進行各個組件的stop和destroy操做。從上述分析能夠看出,要想中止Tomcat,就是要中斷main主線程的等待狀態。工具
下圖爲Tomcat的整個生命週期。spa
(Tomcat生命週期)線程
一、咱們下載的Tomcat壓縮包的bin目錄下,有一個由官方提供的腳本(shutdown.sh),能夠用來結束Tomcat進程。
二、服務器上,咱們還能夠利用kill -x命令來結束Tomcat進程。
三、此外,代碼中的System.exit()以及OOM等異常狀況的發生,也會致使Tomcat進程的關閉,可是這二者都不是正常的運維手段,在此咱們不作分析。
查看分析官方的shutdown.sh腳本以及catalina.sh腳本,發現這兩個腳本最終是在調用Bootstrap類的main方法,和啓動Tomcat時調用的是同一個方法,差別在於傳入了"stop"做爲main方法的參數,而傳入了該參數的main方法,會調用Catalina類的stopServer()方法。在此咱們抹去不須要關注的代碼,能夠把整個stopServer()方法簡化爲以下4步:
其主要作了兩件事:
至此,顯而易見的,這對應了第一小節中的第三種阻塞狀況,"SHUTDOWN"字符串讓main主線程結束了等待狀態,並在接下來經過調用各組件的stop()和destroy()方法進行資源的釋放。
雖然shutdown腳本是由Tomcat官方出品,可是其在實際應用中並不普遍,主要是因爲下面兩個缺點:
Linux中的kill -x操做是向目標進程發送對應的信號量。能夠用kill -l命令查看每一個數值所表明的信號量的值。
(kill信號量)
這裏面,咱們常常會使用kill-9這一命令,kill -9會當即強制結束當前進程,這個操做既方便,但同時也極具破壞性。在實際的環境中,咱們可能有在running的任務,若是此時程序被強制關閉,便會致使當前任務數據的丟失,特別是時間特別長的任務,極有可能形成前功盡棄的局面。同時,若是程序設計不當,沒有相應的冪等操做,還有可能會形成實際環境中數據缺失或者髒數據的產生,對生產環境形成致命的問題。
相比kill -9, kill -15(15只是一個例子,Linux中還有其餘的中斷信號)會相對優雅不少。kill -15是向進程發送一個TERM的中斷信號量,在JVM接收到該信號量後,會響應中斷,進而結束當前進程。而這一操做可以優雅關閉Tomcat的緣由在於,JVM在結束當前進程前,會啓動一系列名爲shutdownhook(關閉鉤子)的線程,而這些線程就會成爲咱們進行風險控制的工具。接下來咱們首先看看Tomcat中的關閉鉤子。
Tomcat的關閉鉤子的定義是在Catalina類中,有一個名爲CatalinaShutdownHook內部類,繼承了Thread類。跟着這個線程類中的run()方法往下看,其調用了Catalina的stop()方法,而此處stop方法,除了正常去中止各組件外,還會去中斷並快速結束main主線程(若是主線程還存在的話),最後再調用各組件的destroy()方法進行資源釋放。
(Tomcat中的shutdownhook)
除了Tomcat會使用關閉鉤子外,不少中間件也會使用到這一很是重要的功能。
咱們在日常的開發過程當中也可使用關閉鉤子,能夠在程序啓動或者運行階段經過調用Runtime.getRuntime().addShutdownHook(shutdownHook)方法進行鉤子的添加,但要注意的是,須要在關閉的流程中加入移除鉤子的代碼。
Spring中固然也有關閉鉤子的應用,而且還爲咱們使用關閉鉤子提供了更爲友好的編程體驗。
在Spring中,關閉鉤子是在AbstractApplicationContext.registerShutdownHook()方法中添加的(下圖中的代碼),而其關閉鉤子的run方法則會調用destroyBeans()方法,其對全部繼承了DisposableBean接口的類調用其destroy()方法。
讀到這裏咱們就明白了,在平時開發時,若是有使用關閉鉤子的需求,能夠經過繼承DisposableBean,並實現其destroy(),很方便的來達到咱們回收資源,打掃戰場的目的。
shutdownhook在使用中也並非能夠隨意亂用的,須要注意如下幾點:
本文對Tomcat兩種經常使用關閉方式的原理進行了解讀,從上述分析能夠看出,用shutdown.sh腳本控制Tomcat關閉的方式存在權限的風險,而且也會因爲開發中的線程操做致使Tomcat沒法關閉,因此這種方法在實際應用中使用狀況較少。
而kill -15則可以安全的殺死Tomcat進程,而且因爲JVM shutdownhook的存在,咱們能夠對整個程序關閉時進行更強有力的控制,退出過程也更爲優雅,因此使用較爲普遍。
更多內容敬請關注 vivo 互聯網技術 微信公衆號
注:轉載文章請先與微信號:labs2020 聯繫。