以前有文章已經介紹過了JVM中的類加載機制,JVM中經過類加載加載class文件,經過雙親委派模型完成分層加載。實際上類加載機制並不只僅是在JVM中得以運用,經過影響字節碼生成和類加載器目前已經有了許多相關的技術誕生。特別的對於進行應用服務器的開發過程當中,類加載機制幾乎是必須掌握的。java
作Java開發的確定都有用過tomcat,回想一下咱們使用tomcat是的場景。最初的時候使用tomcat大多都是單純的使用其做爲項目的容器,而沒有考慮多着中間的不少問題。web
最初咱們使用都是在一臺tomcat上部署多個項目,而後經過端口進行區分。那麼這裏就存在着一個問題,咱們都知道一臺tomcat會啓動一個JVM,部署在tomcat 的應用其實是跑在這一個jvm中的。spring
相信你們多多少少都遇到過jar包衝突的問題。不一樣的項目會有不一樣的依賴jar包,多是不一樣類型的包,也多是相同包的不一樣版本問題。前一文章中又提到jvm校驗類的惟一性是經過類加載器+全限定名
完成的。若是隻是前文中提到的幾中類加載器的話那麼在加載多個項目時確定會出現不一樣版本的jar包覆蓋的狀況,那麼程序一跑起來就報拋出ClassNotFoundException
的異常。tomcat
那麼很明顯tomcat須要對不一樣的項目進行隔離,保證各個項目不會相互影響,這正好能夠經過類加載器來完成。bash
上面講到了不一樣項目間須要進行隔離,而除了隔離還須要考慮共享的問題。Java開發中其臃腫的體系一直爲人詬病,一個很小的web一個項目會有這一大堆的依賴包。若是在一臺tomcat中部署多個項目,每一個項目都須要單獨將依賴類加載到JVM中。這樣致使的後果一是佔據了一部分存儲空間;二是消耗了大量的內存,每一個項目都單獨加載一次,在啓動tomcat後可能會存在大量重複的Class對象(這裏的重複不是JVM意義上的重複,只是說來源於同一個包),那麼項目運行可用內存變少使得GC變得頻繁。服務器
解決這個問題咱們能夠將多個項目中共同依賴的包抽出來讓JVM只加載一次。這樣既不會佔據多的存儲空間,對內存的消耗也將減小。markdown
tomcat做爲一個應用服務器性能確定是很是須要考慮的事情,咱們都知道JVM在加載Class文件時是沒有辦法直接定位到一個具體的文件的,只能搜索指定目錄下的的jar包的內容,那麼這裏的問題就是若是不少的web項目依賴了大量的jar文件,這時要加載一個Class文件就可能會搜索這全部的jar才能加載到該類,這樣對性能的影響就很大了。app
hotSwap也叫熱加載,以前在查找資料的時候發現很多文章都沒弄清楚熱加載和熱部署的區別,這裏簡單說笑。webapp
/ | 熱部署 | 熱加載 |
---|---|---|
實現方式 | 發生修改時整個項目從新打包部署 | 只替換被修改過的類 |
使用場景 | 生產可使用 | 開發時使用,由於開發中須要頻繁的調試代碼 |
熱部署是在當代碼被修改後從新打包整個項目後從新部署,實現方式有多種,目前不少應用服務器都有這個功能,還有一些第三方工具也能夠作到。可是不建議在開發中打開該功能,不少人不明白熱部署和熱加載的區別,結果在開發時使用了熱部署結果致使可能沒修改一次代碼而後就後臺跑一個打包部署的程序,使得電腦變得很卡。jvm
熱替換的典型應用就是對於JSP應用的運行了。咱們都知道JSP應用運行是先將JSP翻譯爲Servlet.java,而後將.java編譯爲.class文件。同時因爲jsp中將邏輯代碼和頁面放在一塊兒了,其修改的機率很高,若是每次修改都須要手動重啓項目那會嚴重影響開發效率,但若是使用熱部署確實能解決問題,但也會電腦卡死的風險。因此咱們但願的是在修改後可以只從新加載被修改過的文件,其餘沒有修改的不懂。
經過上面的問題咱們能夠大概的整理出一個tomcat中的類加載模型。
首先JVM中已經定義好的幾個類加載器確定是不能少的。
要實現隔離咱們得先理清哪些地方須要進行隔離。
首先部署的不一樣的項目之間確定是須要進行隔離的,防止出現包覆蓋,各個項目相互之間要隔離那確定就須要每一個項目有一個獨屬於本身的類加載器,這樣纔可以保證類加載器+全限定名
的惟一性。
其次呢咱們知道tomcat自己也是Java語言進行開發的,那麼它自己確定也會有依賴的jar包,那麼加載tomcat依賴也須要一個類加載器。爲何加載tomcat依賴不能使用JVM提供的類加載器呢?由於假如使用了JVM的類加載器加載了jar包後,若是其餘項目有依賴相同的jar,那麼根據雙親委派模型又會存在包覆蓋的問題了。
到這裏咱們知道了每一個web項目須要一個類加載器,加載tomcat依賴包也須要一個類加載器。
爲了利用好JVM的內存,共享依賴的jar也是頗有必要的,在實現隔離性時對每個web項目都準備了一個類加載器。而共享是要求在加載共享的這一部分jar文件時不從當前項目中加載,而是使用共享的jar文件。
加載共享的jar確定也須要一個類加載器,根據其使用的特性來講加載共享jar的類加載器和web項目的類加載器還存在着一個層級關係。
熱部署是從新加載整個項目,那就很明朗了,若是從新加載那就表明之前加載的就失效了,咱們能夠直接廢棄掉以前項目的類加載器,tomcat從新生成一個新的加載當前項目的類加載器便可。
熱加載就麻煩點了,熱加載要求在修改文件後不從新打包部署項目也可以直接使用。那麼直接從新加載整個項目就不可取了。
一種方法是首先卸載掉被修改的文件咋JVM中已經存在的Class,而後從新加載修改後的文件。理論上來講這是可行的,可是實際上Class的卸載條件自己及其苛刻,並且Class的卸載時有GC完成的,咱們沒有辦法主動的完成卸載的這個過程,因此這一方法就不可行了。
另外一個方式就是給每個jsp建立一個類加載器,這個類加載器只負責這一個jsp,當文件被修改後將這一個類加載器無效,而後從新建立一個新的類加載器來加載便可。
而對每個web項目都建立一個類加載器使得在加載時也不會去從別的項目中搜索jar,就不會存在每次加載好事好久的問題了。
根據上面的分析咱們能夠得出一個tomcat中的類加載結構。
最上層是由JVM提供的幾個默認的類加載器,tomcat類加載器來加載tomcat自己須要的依賴包,共享jar類加載器加載在不一樣項目間共同依賴的jar。
實際的tomcat的類加載結構以下圖:
想較與咱們本身設計的多了一個common Classloader,在默認狀況下咱們並不會使用到共享的功能,基本上都是由web類加載器來加載整個項目,因此默認狀況下tomcat都不開啓shared classloader和Catalina classloader,不開啓的狀況下默認都將全部這部分功能有common classloader代替。
這裏有一個問題須要討論的是tomcat這種類加載模型是否破壞了雙親委派模型呢?
答案是確定的,要實現上面的功能,那麼根據jar的功能其確定是被其指定的classloader進行加載而不會繼續往上推(好比共享的jar在Shared Classloader就直接加載了而不會繼續推向Common Classloader)。這明顯是不符合雙親委派模型的。
並且在tomcat的類加載模型中,假設一部分被共享的jar由Shared Classloader進行加載(好比spring)。咱們都知道spring做爲一個ioc容器必然是須要訪問到咱們web應用中的類的,若是要在spring中加載須要的類這時使用的classloader就是加載spring的classloader,而用戶程序顯然是放在/WEB-INF目錄中的,加載該目錄的classloader倒是web classloader。在上面的類加載層級關係中咱們能夠看到這兩個加載器是上下級關係,可是雙親委派模型中要求向上查找而不能向下查找,那麼很明顯若是遵循雙親委派模型的話功能就沒法正常運行了。
解決這個問題也很簡單,JVM團隊提供了一個線程上下文類加載器( Thread Context Class Loader)。這個類加載器能夠經過Thread類的setContextClassLoaserO
方法進行設置,使用該方法就可讓父類加載器請求子類加載器去完成類加載的動做,這也會打破雙親委派模型的層次結構來逆向使用類加載器。
解決上面問題就是經過spring使用線程上下文加載器來加載類,而線程上下文加載器默認設置爲了WebAppClassLoader,那麼這時是哪個Web應用調用了spring,spring就會用該應用的WebAppClassLoader來加載須要的bean。
Tomcat默認狀況下僅使用common classloader來加載自身的依賴和共享的依賴。若是咱們要啓用Catalina Classloader或者Shared Calssloader須要本身進行手動配置。
下圖爲Tomcat8.0的目錄結構:
默認狀況下Common ClassLoader加載的jar都放在lib包下。
若是咱們要進行配置須要找到conf/catalina.properties文件。找到common.loader
,server.loader
,shared.loader
三個參數,這三個參數分別設置Common ClassLoader,Catalina ClassLoader,Shared ClassLoader的加載的jar包的位置。
參數值能夠是一個指定目錄下的全部包,能夠是指定的jar包,也能夠是包名符合必定規則的jar包。
該配置文件還能夠配置哪些指定名稱的jar包不進行加載。
要想使用熱加載或者熱部署也須要修改配置文件,在conf/Catalina/localhost文件夾下新建一個xml文件,設置內容爲:
//熱加載 //docBase指項目路徑,可使用絕對路徑或相對路徑,相對路徑是相對於webapps <Context docBase="D:\demo\WebRoot" path="/demo" reloadable="true"/> 複製代碼
//熱部署 //熱部署只須要將reloadable置爲false便可,這裏存在一個屬性autoDeploy默認爲true,表示支持熱部署 <Context docBase="D:\demo\WebRoot" path="/demo" reloadable="false"/> 複製代碼
其實並不必定都須要添加新的xml文件,咱們也能夠找到conf/server.xml中在下添加標籤,值和這裏一致。
//<Host>標籤中autoDeploy=true,表示默認支持熱部署, //這樣只要tomcat在運行中,咱們將war包放入到webapps下tomcat就會幫咱們自動的部署 <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> </Host> 複製代碼