考察對類加載的理解(答案篇)

獨立進程篇

首先需要知道類加載器是怎麼回事? 在Java裏邊,類加載器就是用來加載類的,然後纔是執行代碼。
Java裏邊默認有啓動類加載器(boot),擴展類加載器(ext),和應用類加載器(app)。
其中boot就是用來加載最開始的虛擬機和最基本的java類,ext是用來加載一些擴展類,默認是在jre/lib/ext目錄下的。
最後一個纔是你真正會用到的。cp參數就是用來指定應用類加載器找類的地方,java.ext.dirs就是用來指定
擴展類加載器找類的地方。
這三種類加載器是有層級關係的,類似於繼承關係(父子關係)。

另外一個需要知道的概念是,類加載器在找類的時候,默認是採用雙親委派機制的。
例如應用類加載器加載類的時候,會先叫ext去加載,ext就會叫boot去加載,只有加載不到才嘗試自己去加載。
這種做法是有個重要的因素,就是爲了安全性考慮。

最後需要知道的是,TestServlet.class.getClassLoader()獲取到的時候,加載TestServlet類所使用的實際的類加載器。

回到上面的問題。

第一個很簡單,指定了應用類加載器加載的路徑,所以main方法能找到,TestServlet也能找到,config.properties也能被應用類加載器找到。
啓動是正常的。

第二個問題,TestServlet是被擴展類加載器加載的,所以通過它來找config.properties會找不到(在應用類加載器中才能被加載到)。

第三個問題,調整目錄後,這個時候擴展類加載器的加載路徑上也有config.properties,所以啓動也會正常。

現在調整了代碼,換成了Thread.currentThread().getContextClassLoader()的實現。
這個是有一點不一樣的,上下文類加載器默認就是應用類加載器(如果通過代碼進行修改的話)。
上下文類加載器還是一個很有用的技術,
可以用在JDBC這種SPI(Service Provider Interface)接口與實現分離的技術上,有興趣可以去找找資料。

在這種情況下,後面的三個命令都是能夠正常的,因爲config.properties能夠被正常識別的。

再說一點,用eclipse可以運行的程序,用命令行不一定可以,這點必須要認準最後的啓動參數,通過這個來確認。
我們這邊寫獨立進程的時候,貪方便喜歡用java.ext.dirs這個虛擬機參數,但這個有時候會有奇怪的問題。
大家要注意區分這個參數和cp參數的區別,這樣就分析有思路,找問題很happy。

Web應用服務器篇

像tomcat這種應用服務器,本身也是一個java程序,但它可以把我們放上去的各個web應用隔離開來。
你沒法調用其他web應用中的類,看上去好像是完全不相干的。這種技巧就充分利用了自定義類加載器的功能。
像tomcat會對每個應用單獨定義一個類加載器(繼承應用類加載器),並且修改雙親委派機制。
而是採用先從應用中的lib目錄、classes目錄嘗試加載,沒找到纔到上面去找。(像was這種就跟複雜了,加載順序也是可選的)

所以通常我們會有共享庫的概念,在tomcat中對應的就是tomcat_home/lib這個目錄(老版本的話還有更多目錄可選)。
把一些第三方庫放到這裏,可以減少加載類的數量,從而減少內存佔用。

這裏要說明的是,類可以被不同的類加載器加載,雖然是在同一個jvm裏邊,但是是被當成不同的類(唯一標識是類加載器+類名)。

現在回到問題。雖然一開始就有人告訴我們,servlet是屬於單例運行的。但是在這裏有點小變化。

第一個問題,這個應該最常規的做法了,appa和appb是不相干的,所以訪問appb,輸出的是”1 1”,因爲兩個類是通過不同的類加載器加載的(就是說不一樣的類),肯定生成的servlet實例是不一樣的。

第二個問題,這種是共享庫的做法,所以實際上他們使用的是同一個類,但是對於不同的app,用的是不同的servlet實例。
所以會出現靜態變量有影響,當實例變量是獨立的。所以最後會輸出”3 1”。所以有維護靜態變量的話,使用共享庫是有不一樣的。

第三個問題,這種其實在生產中也很常見,上新程序的時候就可能變成這樣了。其實這個跟第一種情況是一樣的。
不過,在was上加載順序是可選的,所以情況也可能變成第二種情況。

後話

這裏講解的只是皮毛,有興趣的童鞋,可以google更多資料和書籍,加以研究。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章