va是非常簡單精巧的語言,背後的基本原來也很簡單,總的說來有兩點:
1 . JVM的記憶體管理,理解了這個,有關對象的問題都能解決。比如安全執行緒問題,記憶體泄露問題等。
2.JVM的類載入體系,理解了這個,有關jar包的配置問題,包括各種appServer的配置,應用的發布問題都能解決。
有關JVM的記憶體管理,只要理解了以上的圖,基本上就能理解得八九不離十。本文檔主要講解JVM的類載入體系,在我們的平常開發中,大多使用了預設的類載入器,不需要深入理解類載入原理。但如果你不僅僅滿足於平時的開發,想深入瞭解一些底層原理,或者想閱讀一些開源軟體的原始碼,如tomcat,jdbc等的實現,則有必要深入研究類載入的原理。本文檔就是為想成為java高手的人準備的。
本文不準備對基本的類載入原理做過多的描述,因為此類文章在網上多牛毛,不瞭解類載入原理的同學請參考JAVA物件導向編程 一書的第十章(類的聲明周期)或者java深度曆險。下面摘取幾個主要的圖來說明大致的原理。
圖1是java的類載入體系,有BootStrap , Ext , App和使用者自訂載入器,載入類的時候採用雙親委託機制。特別需要注意的是:載入器ClassLoader的父子關係和繼承關係無關,也和載入器是有哪個載入器載入的無關,而是在建立載入器時指定的父載入器有關,即是由人工指定的。比如說要確定載入器A的父載入器,僅僅是由建立對象A時傳進去的父載入器決定,而不管A的類型是什麼,也不管A是由哪個載入器載入的。
載入Class的預設規則比較簡單,有兩個:
1, 雙親委託機制。
2, 同一個載入器:類A引用到類B,則由類A的載入器去載入類B,保證引用到的類由同一個載入器載入。
圖2是載入器,Class對象,和類在記憶體中的組織形式。載入器,Class對象都是執行個體對象,都是存在heap區。而類的相關資訊則存在方法區,也就是Perm 區。(BTY:如果方法區滿了,就會拋出java.lang.Out of Memory Perm Gem Space Error的異常)。
圖3是java程式的執行過程,可以看到執行時是先載入如整個類載入體系,然後用此體系載入我們要執行的類,然後運行main方法。
圖四是jvm載入類的三種方法,包括隱式的new,和顯式的Class.forName()和ClassLoader.loadCLass().
圖1
圖2
圖三
圖四
在大部分情況下,jvm的這個預設類載入體系已經能滿足大部分情況的使用了。那為什麼還需要ContextClassLoader呢。這其實是因為載入Class的預設規則在某些情況下不能滿足要求。比如jdk中的jdbc API 和具體資料庫廠商的實作類別SPI的類載入問題。在jdbc API的類是由BootStrap載入的,那麼如果在jdbc API需要用到spi的實作類別時,根據預設規則2,則實作類別也會由BootStrap載入,但是spi實作類別卻沒法由BootStrap載入,只能由Ext或者App載入,如何解決這個問題。牛人們就想出了ContextClassLoader的方法。
具體的實現過程如下:在類Thread定義一個屬性classLoader,用來供使用者儲存一個classLoader(預設是App),並公開setter和getter方法,使得此線程的任何語句都可以得到此classLoader,然後用它來載入類,以此來打破預設規則2。說白了,ContextClassLoader就是Thread的一個屬性而已,沒什麼複雜的。只不過這個屬性和底層的載入體系聯絡緊密,使得很多人以為有什麼高深的原理在裡面。明白了這一點,ContextClassLoader也就沒什麼神秘的了。
那麼我們看看ContextClassLoader是如何解決上面的問題的呢。以jdbc為例,當DriverManager需要載入SPI中的實作類別是,可以擷取ContextClassLoader(預設是App),然後用此classLoader來載入spi中的類。很簡單的過程。當然不使用ContextClassLoader,自己找個地方把classLoader儲存起來,在其他地方能得到此classLoader就可以。Tomcat就是這麼做的。比如StandardContext是由Common載入的,而StandardContext要用到項目下的類時怎麼辦,顯然不能用Common來載入,而只能用WebAppClassLoader來載入,怎麼辦。當然可以採用ContextClassLoader的方式來解決,但是tomcat不是這樣解決的,而是為每個App建立一個Loader,裡面儲存著此WebApp的ClassLoader。需要載入WebApp下的類時,就取出ClassLoader來使用,原理和ContextClassLoader是一樣的。至於tomcat為什麼要這麼做呢。因為tomcat中有關類載入的問題,是由一個main線程來處理的,而並沒有為每個WebApp單獨建立一個線程,故沒辦法用ContextClassLoader的方式來解決,而是用自己的屬性來解決。
Tomcat6的類載入器體系,來自:http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html
既然規則2已經被打破了,那麼規則1:雙親委託機制能被打破嗎。規則1是因為安全問題而設定的,如果我自己能控制類載入的安全問題,是否可以違反規則1呢。答案是肯定的。同樣的Tomcat也打破了此規則。Tomcat中WebAppClassLoader附加元件目的類時,可以決定是否先在項目本地的路徑中尋找class檔案。而不是自動使用父載入器Common在tomcat下的common目錄尋找。這是怎麼實現的呢。
要回答這個問題,就要先明白雙親委託機制是如何?的。只有明白了如何?,才有辦法改變它。雙親委託機制是在類ClassLoader的loadClass裡面實現的。那麼繼承ClassLoader的WebAppClassLoader,只要覆蓋loadClass方法,實現自己的載入策略,即可避開雙親委託機制。