Tomcat 啓動初始化和停止

  1. 調用 StandardServer.initialize 開始組件的初始化,觸發 INIT_EVENT 事件,詳細過程:
  2. 初始化 StandardServer:首先觸發各 Listener(具體有哪些,可查看xml的配置)的執行,然後初始化內部的 Services;
  3. Service 主要是初始化定義的 Connectors;
  4. 初始化 Connector:初始化 Adapter 和 ProtocolHandler,處理器有兩種不同實現:
  • Http11NioProtocol 初始化:NioEndpoint 設置線程池名稱、設置ConnectionHandler、設置接收和發送 ByteBuffer 容量;SSL相關實現初始化;初始化 NioEndpoint:初始化 ServerSocketChannel,設置成阻塞模式,綁定端口;設置 Acceptor、Poller 線程數目;初始化 SSL 信息;初始化 NioSelectorPool;
  • Http11Protocol 初始化:JIoEndpoint 設置線程池名和ConnectionHandler,初始化 ServerSocketFactory;初始化 JioEndpoint:設置 Acceptor 線程數;創建 ServerSocket 並綁定端口。
  1. 初始化完畢

以上就是在組件在init生命週期事件中完成的設置,注意在 Digester 解析過程中,也完成了一系列的設置。

啟動

調用 StandardServer.start 開啟組件的啟動過程,觸發 BEFORE_START_EVENT、START_EVENT、AFTER_START_EVENT 事件:

  1. 啟動 StandardServer:觸發執行各 Listener;啟動內部的 Services;
  2. Service 默認沒有 Listener,首先啟動 Engines 、接著啟動 Executors、最後啟動 Connectors;
  3. 啟動 Engine,它調用 super.start() 進行啟動或觸發以下的動作:
  • 嘗試啟動 Manager、Cluster、Realm(LockOutRealm);
  • 啟動子容器 Hosts;
  • EngineConfig 監聽器的執行 - START_EVENT,STOP_EVENT;
  • 啟動後臺線程,定期檢查會話超時。
  1. 啟動 Host:設置 ErrorReportValve,並添加到自己的 pipeline 中,觸發 ADD_VALVE_EVENT 容器事件,調用 super.start():
  • 啟動子容器 Contexts;
  • 啟動 pipeline 中實現 Lifecycle 的 Valve。
  • 觸發 HostConfig 監聽器的執行:
  • PERIODIC_EVENT:檢查所有Web應用程序的狀態;
  • START_EVENT:創建 Context,啟動並部署 webapps & conf/Catalina/localhost/*.xml;
  • STOP_EVENT:取消已部署的所有應用。
  1. 啟動 Context,部署 Web App
  • 根據 context.xml(或默認的)使用 Digester 創建 StandardContext 對象,添加 ContextConfig 監聽器,通過 host.addChild 啟動 Context;
  • 初始化用於解析 web.xml 的 Digester,創建設置 WebappLoader 且不使用”標準委託模型”;
  • 先解析默認的 conf/web.xml,然後處理 WEB-INF/web.xml,創建 StandardWrapper 封裝 Servlet。
  1. 啟動 Connector,無 Listener,它主要是啟動 ProtocolHandler:
  • Http11NioProtocol - 啟動 NioEndpoint - 啟動 Poller 線程,啟動 Acceptor 線程;
  • Http11Protocol - 啟動 JioEndpoint - 啟動 Acceptor 線程。
  1. 註冊 ShutdownHook,阻塞 main 線程,啟動完畢。

接下來就該處理請求了,這部分下一篇會介紹。

停止

當調用 Bootstrap.stop 或接收到 SHUTDOWN 指令或者捕捉到系統關閉信號(如ctrl+c,shutdown,logoff)時,開始調用 Catalina.stop 方法,關閉組件,觸發 BEFORE_STOP_EVENT、STOP_EVENT 事件:停止Server、Service,暫停 Connector,停止內部組件,停止 Connector,關閉線程池,打斷 awaitThread 即 main 線程,結束。

如果 stop 命令執行失敗,嘗試使用系統信號終止,首先使用 kill -15 ,進程收到後進行處理;如果還是失敗那麼,等待 5s 後,使用 kill -9 強制終止。

小結

當我看到 Web應用加載的時候,耐心有點不足,感覺捋順了整個過程,其實還有很多地方經不起推敲,為了看得更透徹,那麼這個”簡單”的啟動過程,又有哪些值得思考的呢?

內部啟動的線程和線程池中的線程為什麼設置為守護線程(Daemon)?

Java 中有兩類線程:守護線程與用戶線程,區別是守護線程不會阻止 JVM 的退出。從 Tomcat 的停止流程來看,就算用非守護線程也不會出現什麼問題,沒有搜到官方對此的描述,這裡按照理解強行解釋一波 :)。

守護線程一般是服務提供者,運行系統代碼,比如 GC 線程,就像操作系統中也區分用戶線程和內核線程那樣,Tomcat 將內部線程設為 daemon,也能很好的在語義上區分 Servlet 啟動的線程,並且對於 Servlet 來說 Tomcat 就是它的操作系統。

生命週期的設計有什麼好處?

保證組件啟動和停止的一致性,為生命週期事件添加監聽器,這些監聽器處理其感興趣的事件,來做一些額外的操作。這是觀察者模式的應用。

多應用間如何實現隔離?

應用隔離的本質就是類隔離,主要防止類衝突。類是否相等是由其全限定名和類加載器共同決定的,容器就是通過自定義 ClassLoader 實現應用間隔離,Tomcat 類加載器結構:

Tomcat 啟動初始化和停止

當要求類加載器加載類時,它首先將請求委託給父加載器,然後在父加載器找不到所請求的類時,查找自己的存儲庫。而 Webapp 加載器略有不同,它首先會在自己的資源庫中搜索,而不是向上委託,打破了標準的委託機制,其類加載時按以下順序查找資源庫:

  • Bootstrap 和 System 已加載的類
  • /WEB-INF/classes 和 /WEB-INF/lib/*.jar
  • Common 已加載的類

應用熱加載和熱部署?

當在啟動 Engine 時,會新建一個名為 ContainerBackgroundProcessor[StandardEngine[Catalina]] 的線程,默認 10s 檢查是否要重新加載或重新部署,對應方法在 Loader 接口定義分別是 backgroundProcess 和 modified。

默認 web.xml 配置了什麼?

  • 提供一個 DefaultServlet,用於處理靜態資源和未找到匹配 Servlet 的請求;
  • 用於編譯和執行 JSP 的 JspServlet;
  • Session 默認超時 30 minutes;
  • 默認 MIME Type 映射和 Welcome Files

其他能夠想到的點

採用都是常用的數據結構,如 ArrayList、數組、HashMap等,Pipeline 採用鏈表實現,Digester 使用棧來解析。歡迎補充。

列表好像不支持2、3級嵌套啊...行文中部分鏈接無法添加,如需要可點擊瞭解更多查看!


分享到:


相關文章: