1 問題背景
我們都知道,Java平臺一大亮點就在于其類裝載器體系結構,這使得JVM可以在運行期從Java API,擴展路經(java.ext.path),classpath以及用戶指定的位置(文件或網絡)中載入所需的class,從而達到動態裝載的目的。然而其類裝載器委托模型在保證了安全性和強大功能的同時,也導致了相當的復雜性,有很多地方一旦我們不加注意的話就將導致錯誤。這里我希望通過一些小例子來展示動態裝載的某些方面,深入地了解一下怎么進行動態裝載,會遇到什么樣的問題,并就問題的原因與解決方法進行討論。
也許有人會說:我的程序不用什么動態裝載,平時運行程序就是java –classpath … myPkg.my不就行了嗎?不過我總是聽前輩說,要想完全了解一個系統下的程序設計,就必須要深入研究這個平臺的特性,做到心中有數,做起程序才不會處處制肘。類的動態裝載是Java平臺最顯著的特征之一,許多著名的項目—Tomcat,Eclipse都使用自定義類裝載器來裝載運行時所需的類,如果連什么是動態裝載和怎么動態裝載都沒弄明白,還能去玩自定義類裝載器嗎?所以還是不要浮躁,讓我們從基礎開始做起吧。
不過,我要說明的是你在看我這篇文章之前最好先熟悉一下Java的類裝載器體系結構以及其委托模型,我在這里將不加贅述,推薦先看《Java深度歷險》或《深入Java虛擬機》的第八章。
2 問題研究
我要討論的問題是:讓一個在my/目錄下名為my.Main的類在運行期讀取util/中的util.Tool類。
l 實驗一
我的實驗目錄結構如下
其中my.Main的代碼如下:
util.Tool的代碼如下:
在my.Main的代碼中16行可以看到,我定義了一個URLClassLoader,想要讀取在同一根目錄下的/util/Tool.class,接下來我們運行如下命令,來查看運行結果:
如上可見,util.Tool被順利裝載了,可是裝載它的卻是AppClassLoader,而不是我們想要的URLClassLoader!這是為什么呢?
原因很簡單,因為我們將util/Tool.class放到了JVM系統變量”user.dir”所指定的目錄下,而該JVM系統變量的默認值為程序所在的根目錄,相當于把classpath 設置成了”.”,也就是相當于執行了命令:
java –classpath . my.Main
于是,正因為util.Tool在classpath下,于是JVM就在在載入my.Main的同時也用AppClassLoader將util.Tool載入到JVM了。我們可以通過如下命令看得更清楚一些:
java –verbose my.Main
看到了嗎?由于AppClassLoader預先載入了util.Tool類,而且在URLClassLoader的loadClass()方法中有如下語句:Class cls = getLoadedClass(String className);如果cls!=null的話,loadClass()就會返回已經載入過的(即由AppClassLoader所載入的)util.Tool類,所以我們才會看到以上結果。
l 實驗二
為了解決以上問題,我決定按如下方式組織我的目錄結構
然后對Main.class進行一些修改:
嗯,這下我們不會為util.Tool被提前載入而煩惱了,因為載入my.Main的AppClassLoader找不到換了位置的util.Tool類了。這回可能有人會問:subdir不是還在”user.dir”所指向的目錄下嗎?怎么只是多建了一層目錄它就找不到了呢?原因是AppClassLoader是URLClassLoader的子類,它在載入class的時候也是根據該類的URL來進行定位的。那我們的例子來說,在“實驗一”的最后一個圖中我們可以看到,util.Tool被載入的位置為:file1:c:/Test1,JVM得到這個URL之后,將util.Tool中的”.”變為”/”,在所得字符串結尾處加上”.class”,最后將該字符串加到file1:c:/Test1后面,就得到了最終util.Tool的URL:file1:c:/Test1/util/Tool.class。而當加了一層subdir目錄之后,AppClassLoader就找不到file1:c:/Test1/util/Tool.class了,而只能由程序中定義的URLClassLoader來載入。
這時我們運行程序,所得結果如下:
可以看到,util.Tool確實已經我定義的URLClassLoader被載入并初始化了,可是為什么最后卻有一個奇怪的異常呢?NoClassDefFoundError?util.Tool類不是都載入了嗎?怎么還找不到它的定義呢?
這個問題需要從JVM的裝載機制說起。對于每個JVM實例,除了內置的三個內置的類裝載器(BootStrapClassLoader,ExtClassLoader,AppClassLoader)之外,還可能有一些用戶自定義的類裝載器,而這些類裝載器可能在一個程序運行周期內載入同一個類,即AppClassLoader和我自定義的類裝載器MyClassLoader可能同時需要載入util.Tool類。為了避免沖突,JVM為每個類裝載器都建立一個命名空間,并以此作為其訪問邊界。例如,如果my.Main想要引用util.Tool類,則它們必須是由同一裝載器載入的,也就是說,由于my.Main是由AppClassLoader載入的,那么util.Tool也必須由AppClassLoader載入才行,否則就會出現上述錯誤。
問題解決的方法很簡單,只需要讓my.Main和util.Tool由同一類裝載器載入就行了。
l 實驗三
為了讓my.Main和util.Tool由同一類裝載器載入,我定義了一個啟動類—boot.BootStrap類:
在這里,我用自己的類裝載器ucl來裝載my.Main類,并用reflection API來調用其main()方法,同時也對my.Main稍作修改
然后執行一下看看結果:
圖中可以很清楚的看到BootStrap類則是由AppClassLoader載入的,而Main類和Tool類都是由同一個URLClassLoader在運行期動態載入的。至此,我們完成了對類的動態載入的全部說明和實驗,希望對你能有所幫助。
posted on 2007-07-27 22:28
前方的路 閱讀(359)
評論(1) 編輯 收藏 所屬分類:
Java技術