研究了一下JVM的原理,classloader的用處,感覺(jué)這篇文章不錯(cuò),貼了過(guò)來(lái)。讀完了感覺(jué)java程序在內(nèi)存中的分布就是一棵樹(shù),跟它的classloader的分布是一樣的,不知這樣說(shuō)對(duì)不對(duì),請(qǐng)大家指點(diǎn)一下。
還有,因?yàn)檠芯縯omcat的源碼才研究jvm的,因?yàn)閠omcat里一開(kāi)始就是classloader的應(yīng)用。
目前為止我對(duì)java的WEB項(xiàng)目有兩點(diǎn)很大的疑團(tuán),一:多個(gè)用戶(hù)怎樣運(yùn)行于一個(gè)WEB項(xiàng)目上;二:context的意思,我似懂非懂,表面上像懂了,細(xì)一思考又很迷惑,不知道它確切的說(shuō)是什么東西。有知道的朋友告訴我一下哈,好了,大家請(qǐng)看文章........
Java中類(lèi)的查找與裝載出現(xiàn)的問(wèn)題總是會(huì)時(shí)不時(shí)出現(xiàn)在Java程序員面前,這并不是什么丟臉的事情,相信沒(méi)有一個(gè)Java程序員沒(méi)遇到過(guò)ClassNotException,因此不要為被人瞅見(jiàn)自己也犯這樣的錯(cuò)誤而覺(jué)得不自然,但是在如果出現(xiàn)了ClassNotFoundException后異常后一臉的茫然,那我想你該了解一下java的類(lèi)裝載的體制了,同時(shí)為了進(jìn)行下面的關(guān)于類(lèi)裝載器之間的隔離性的討論,我們先簡(jiǎn)單介紹一下類(lèi)裝載的體系結(jié)構(gòu)。
1. Java類(lèi)裝載體系結(jié)構(gòu)
裝載類(lèi)的過(guò)程非常簡(jiǎn)單:查找類(lèi)所在位置,并將找到的Java類(lèi)的字節(jié)碼裝入內(nèi)存,生成對(duì)應(yīng)的Class對(duì)象。Java的類(lèi)裝載器專(zhuān)門(mén)用來(lái)實(shí)現(xiàn)這樣的過(guò)程,JVM并不止有一個(gè)類(lèi)裝載器,事實(shí)上,如果你愿意的話,你可以讓JVM擁有無(wú)數(shù)個(gè)類(lèi)裝載器,當(dāng)然這除了測(cè)試JVM外,我想不出還有其他的用途。你應(yīng)該已經(jīng)發(fā)現(xiàn)到了這樣一個(gè)問(wèn)題,類(lèi)裝載器自身也是一個(gè)類(lèi),它也需要被裝載到內(nèi)存中來(lái),那么這些類(lèi)裝載器由誰(shuí)來(lái)裝載呢,總得有個(gè)根吧?沒(méi)錯(cuò),確實(shí)存在這樣的根,它就是神龍見(jiàn)首不見(jiàn)尾的Bootstrap ClassLoader. 為什么說(shuō)它神龍見(jiàn)首不見(jiàn)尾呢,因?yàn)槟愀緹o(wú)法在Java代碼中抓住哪怕是它的一點(diǎn)點(diǎn)的尾巴,盡管你能時(shí)時(shí)刻刻體會(huì)到它的存在,因?yàn)閖ava的運(yùn)行環(huán)境所需要的所有類(lèi)庫(kù),都由它來(lái)裝載,而它本身是C++寫(xiě)的程序,可以獨(dú)立運(yùn)行,可以說(shuō)是JVM的運(yùn)行起點(diǎn),偉大吧。在Bootstrap完成它的任務(wù)后,會(huì)生成一個(gè)AppClassLoader(實(shí)際上之前系統(tǒng)還會(huì)使用擴(kuò)展類(lèi)裝載器ExtClassLoader,它用于裝載Java運(yùn)行環(huán)境擴(kuò)展包中的類(lèi)),這個(gè)類(lèi)裝載器才是我們經(jīng)常使用的,可以調(diào)用ClassLoader.getSystemClassLoader() 來(lái)獲得,我們假定程序中沒(méi)有使用類(lèi)裝載器相關(guān)操作設(shè)定或者自定義新的類(lèi)裝載器,那么我們編寫(xiě)的所有java類(lèi)通通會(huì)由它來(lái)裝載,值得尊敬吧。AppClassLoader查找類(lèi)的區(qū)域就是耳熟能詳?shù)腃lasspath,也是初學(xué)者必須跨過(guò)的門(mén)檻,有沒(méi)有靈光一閃的感覺(jué),我們按照它的類(lèi)查找范圍給它取名為類(lèi)路徑類(lèi)裝載器。還是先前假定的情況,當(dāng)Java中出現(xiàn)新的類(lèi),AppClassLoader首先在類(lèi)傳遞給它的父類(lèi)類(lèi)裝載器,也就是Extion ClassLoader,詢(xún)問(wèn)它是否能夠裝載該類(lèi),如果能,那AppClassLoader就不干這活了,同樣Extion ClassLoader在裝載時(shí),也會(huì)先問(wèn)問(wèn)它的父類(lèi)裝載器。我們可以看出類(lèi)裝載器實(shí)際上是一個(gè)樹(shù)狀的結(jié)構(gòu)圖,每個(gè)類(lèi)裝載器有自己的父親,類(lèi)裝載器在裝載類(lèi)時(shí),總是先讓自己的父類(lèi)裝載器裝載(多么尊敬長(zhǎng)輩),如果父類(lèi)裝載器無(wú)法裝載該類(lèi)時(shí),自己就會(huì)動(dòng)手裝載,如果它也裝載不了,那么對(duì)不起,它會(huì)大喊一聲:Exception,class not found。有必要提一句,當(dāng)由直接使用類(lèi)路徑裝載器裝載類(lèi)失敗拋出的是NoClassDefFoundException異常。如果使用自定義的類(lèi)裝載器loadClass方法或者ClassLoader的findSystemClass方法裝載類(lèi),如果你不去刻意改變,那么拋出的是ClassNotFoundException。
我們簡(jiǎn)短總結(jié)一下上面的討論:
1.JVM類(lèi)裝載器的體系結(jié)構(gòu)可以看作是樹(shù)狀結(jié)構(gòu)。
2.父類(lèi)裝載器優(yōu)先裝載。在父類(lèi)裝載器裝載失敗的情況下再裝載,如果都裝載失敗則拋出ClassNotFoundException或者NoClassDefFoundError異常。
那么我們的類(lèi)在什么情況下被裝載的呢?
2. 類(lèi)如何被裝載
在java2中,JVM是如何裝載類(lèi)的呢,可以分為兩種類(lèi)型,一種是隱式的類(lèi)裝載,一種式顯式的類(lèi)裝載。
2.1 隱式的類(lèi)裝載
隱式的類(lèi)裝載是編碼中最常用得方式:
A b = new A();
如果程序運(yùn)行到這段代碼時(shí)還沒(méi)有A類(lèi),那么JVM會(huì)請(qǐng)求裝載當(dāng)前類(lèi)的類(lèi)裝器來(lái)裝載類(lèi)。問(wèn)題來(lái)了,我把代碼弄得復(fù)雜一點(diǎn)點(diǎn),但依舊沒(méi)有任何難度,請(qǐng)思考JVM得裝載次序:
package test;
Public class A{
public void static main(String args[]){
B b = new B();
}
}
class B{C c;}
class C{}
揭曉答案,類(lèi)裝載的次序?yàn)锳->B,而類(lèi)C根本不會(huì)被JVM理會(huì),先不要驚訝,仔細(xì)想想,這不正是我們最需要得到的結(jié)果。我們仔細(xì)了解一下JVM裝載順序。當(dāng)使用Java A命令運(yùn)行A類(lèi)時(shí),JVM會(huì)首先要求類(lèi)路徑類(lèi)裝載器(AppClassLoader)裝載A類(lèi),但是這時(shí)只裝載A,不會(huì)裝載A中出現(xiàn)的其他類(lèi)(B類(lèi)),接著它會(huì)調(diào)用A中的main函數(shù),直到運(yùn)行語(yǔ)句b = new B()時(shí),JVM發(fā)現(xiàn)必須裝載B類(lèi)程序才能繼續(xù)運(yùn)行,于是類(lèi)路徑類(lèi)裝載器會(huì)去裝載B類(lèi),雖然我們可以看到B中有有C類(lèi)的聲明,但是并不是實(shí)際的執(zhí)行語(yǔ)句,所以并不去裝載C類(lèi),也就是說(shuō)JVM按照運(yùn)行時(shí)的有效執(zhí)行語(yǔ)句,來(lái)決定是否需要裝載新類(lèi),從而裝載盡可能少的類(lèi),這一點(diǎn)和編譯類(lèi)是不相同的。
2.2 顯式的類(lèi)裝載
使用顯示的類(lèi)裝載方法很多,我們都裝載類(lèi)test.A為例。
使用Class類(lèi)的forName方法。它可以指定裝載器,也可以使用裝載當(dāng)前類(lèi)的裝載器。例如:
Class.forName("test.A");
它的效果和
Class.forName("test.A",true,this.getClass().getClassLoader());
是一樣的。
使用類(lèi)路徑類(lèi)裝載裝載.
ClassLoader.getSystemClassLoader().loadClass("test.A");
使用當(dāng)前進(jìn)程上下文的使用的類(lèi)裝載器進(jìn)行裝載,這種裝載類(lèi)的方法常常被有著復(fù)雜類(lèi)裝載體系結(jié)構(gòu)的系統(tǒng)所使用。
Thread.currentThread().getContextClassLoader().loadClass("test.A")
使用自定義的類(lèi)裝載器裝載類(lèi)
public class MyClassLoader extends URLClassLoader{
public MyClassLoader() {
super(new URL[0]);
}
}
MyClassLoader myClassLoader = new MyClassLoader();
myClassLoader.loadClass("test.A");
MyClassLoader繼承了URLClassLoader類(lèi),這是JDK核心包中的類(lèi)裝載器,在沒(méi)有指定父類(lèi)裝載器的情況下,類(lèi)路徑類(lèi)裝載器就是它的父類(lèi)裝載器,MyClassLoader并沒(méi)有增加類(lèi)的查找范圍,因此它和類(lèi)路徑裝載器有相同的效果。
我們已經(jīng)知道Java的類(lèi)裝載器體系結(jié)構(gòu)為樹(shù)狀,多個(gè)類(lèi)裝載器可以指定同一個(gè)類(lèi)裝載器作為自己的父類(lèi),每個(gè)子類(lèi)裝載器就是樹(shù)狀結(jié)構(gòu)的一個(gè)分支,當(dāng)然它們又可以個(gè)有子類(lèi)裝載器類(lèi)裝載器,類(lèi)裝載器也可以沒(méi)有父類(lèi)裝載器,這時(shí)Bootstrap類(lèi)裝載器將作為它的隱含父類(lèi),實(shí)際上Bootstrap類(lèi)裝載器是所有類(lèi)裝載器的祖先,也是樹(shù)狀結(jié)構(gòu)的根。這種樹(shù)狀體系結(jié)構(gòu),以及父類(lèi)裝載器優(yōu)先的機(jī)制,為我們編寫(xiě)自定義的類(lèi)裝載器提供了便利,同時(shí)可以讓程序按照我們希望的方式進(jìn)行類(lèi)的裝載。例如某個(gè)程序的類(lèi)裝載器體系結(jié)構(gòu)圖如下:
此處沒(méi)找到圖,見(jiàn)諒
圖2:某個(gè)程序的類(lèi)裝載器的結(jié)構(gòu)
解釋一下上面的圖,ClassLoaderA為自定義的類(lèi)裝載器,它的父類(lèi)裝載器為類(lèi)路徑裝載器,它有兩個(gè)子類(lèi)裝載器ClassLoaderAA和ClassLaderAB,ClassLoaderB為程序使用的另外一個(gè)類(lèi)裝載器,它沒(méi)有父類(lèi)裝載器,但有一個(gè)子類(lèi)裝載器ClassLoaderBB。你可能會(huì)說(shuō),見(jiàn)鬼,我的程序怎么會(huì)使用這么復(fù)雜的類(lèi)裝載器結(jié)構(gòu)。為了進(jìn)行下面的討論,暫且委屈一下。
3. 奇怪的隔離性
我們不難發(fā)現(xiàn),圖2中的類(lèi)裝載器AA和AB, AB和BB,AA和B等等位于不同分支下,他們之間沒(méi)有父子關(guān)系,我不知道如何定義這種關(guān)系,姑且稱(chēng)他們位于不同分支下。兩個(gè)位于不同分支的類(lèi)裝載器具有隔離性,這種隔離性使得在分別使用它們裝載同一個(gè)類(lèi),也會(huì)在內(nèi)存中出現(xiàn)兩個(gè)Class類(lèi)的實(shí)例。因?yàn)楸痪哂懈綦x性的類(lèi)裝載器裝載的類(lèi)不會(huì)共享內(nèi)存空間,使得使用一個(gè)類(lèi)裝載器不可能完成的任務(wù)變得可以輕而易舉,例如類(lèi)的靜態(tài)變量可能同時(shí)擁有多個(gè)值(雖然好像作用不大),因?yàn)榫退闶潜谎b載類(lèi)的同一靜態(tài)變量,它們也將被保存不同的內(nèi)存空間,又例如程序需要使用某些包,但又不希望被程序另外一些包所使用,很簡(jiǎn)單,編寫(xiě)自定義的類(lèi)裝載器。類(lèi)裝載器的這種隔離性在許多大型的軟件應(yīng)用和服務(wù)程序得到了很好的應(yīng)用。下面是同一個(gè)類(lèi)靜態(tài)變量為不同值的例子。
package test;
public class A {
public static void main( String[] args ) {
try {
//定義兩個(gè)類(lèi)裝載器
MyClassLoader aa= new MyClassLoader();
MyClassLoader bb = new MyClassLoader();
//用類(lèi)裝載器aa裝載testb.B類(lèi)
Class clazz=aa.loadClass("testb. B");
Constructor constructor=
clazz.getConstructor(new Class[]{Integer.class});
Object object =
constructor.newInstance(new Object[]{new Integer(1)});
Method method =
clazz.getDeclaredMethod("printB",new Class[0]);
//用類(lèi)裝載器bb裝載testb.B類(lèi)
Class clazz2=bb.loadClass("testb. B");
Constructor constructor2 =
clazz2.getConstructor(new Class[]{Integer.class});
Object object2 =
constructor2.newInstance(new Object[]{new Integer(2)});
Method method2 =
clazz2.getDeclaredMethod("printB",new Class[0]);
//顯示test.B中的靜態(tài)變量的值
method.invoke( object,new Object[0]);
method2.invoke( object2,new Object[0]);
} catch ( Exception e ) {
e.printStackTrace();
}
}
}
//Class B 必須位于MyClassLoader的查找范圍內(nèi),
//而不應(yīng)該在MyClassLoader的父類(lèi)裝載器的查找范圍內(nèi)。
package testb;
public class B {
static int b ;
public B(Integer testb) {
b = testb.intValue();
}
public void printB() {
System.out.print("my static field b is ", b);
}
}
public class MyClassLoader extends URLClassLoader{
private static File file = new File("c:\\classes ");
//該路徑存放著class B,但是沒(méi)有class A
public MyClassLoader() {
super(getUrl());
}
public static URL[] getUrl() {
try {
return new URL[]{file.toURL()};
} catch ( MalformedURLException e ) {
return new URL[0];
}
}
}
程序的運(yùn)行結(jié)果為:
my static field b is 1
my static field b is 2
程序的結(jié)果非常有意思,從編程者的角度,我們甚至可以把不在同一個(gè)分支的類(lèi)裝載器看作不同的java虛擬機(jī),因?yàn)樗鼈儽舜擞X(jué)察不到對(duì)方的存在。程序在使用具有分支的類(lèi)裝載的體系結(jié)構(gòu)時(shí)要非常小心,弄清楚每個(gè)類(lèi)裝載器的類(lèi)查找范圍,盡量避免父類(lèi)裝載器和子類(lèi)裝載器的類(lèi)查找范圍中有相同類(lèi)名的類(lèi)(包括包名和類(lèi)名),下面這個(gè)例子就是用來(lái)說(shuō)明這種情況可能帶來(lái)的問(wèn)題。
假設(shè)有相同名字卻不同版本的接口 A,
版本 1:
package test;
Intefer Same{ public String getVersion(); }
版本 2:
Package test;
Intefer Same{ public String getName(); }
接口A兩個(gè)版本的實(shí)現(xiàn):
版本1的實(shí)現(xiàn)
package test;
public class Same1Impl implements Same {
public String getVersion(){ return "A version 1";}
}
版本2的實(shí)現(xiàn)
public class Same 2Impl implements Same {
public String getName(){ return "A version 2";}
}
我們依然使用圖2的類(lèi)裝載器結(jié)構(gòu),首先將版本1的Same和Same的實(shí)現(xiàn)類(lèi)Same1Impl打成包same1.jar,將版本2的Same和Same的實(shí)現(xiàn)類(lèi)Same1Impl打成包same2.jar。現(xiàn)在,做這樣的事情,把same1.jar放入類(lèi)裝載器ClassLoaderA的類(lèi)查找范圍中,把same2.jar放入類(lèi)裝器ClassLoaderAB的類(lèi)查找范圍中。當(dāng)你興沖沖的運(yùn)行下面這個(gè)看似正確的程序。
實(shí)際上這個(gè)錯(cuò)誤的是由父類(lèi)載器優(yōu)先裝載的機(jī)制造成,當(dāng)類(lèi)裝載器ClassLoaderAB在裝載Same2Impl類(lèi)時(shí)發(fā)現(xiàn)必須裝載接口test.Same,于是按規(guī)定請(qǐng)求父類(lèi)裝載器裝載,父類(lèi)裝載器發(fā)現(xiàn)了版本1的test.Same接口并興沖沖的裝載,但是卻想不到Same2Impl所希望的是版本2 的test.Same,后面的事情可想而知了,異常被拋出。
我們很難責(zé)怪Java中暫時(shí)并沒(méi)有提供區(qū)分版本的機(jī)制,如果使用了比較復(fù)雜的類(lèi)裝載器體系結(jié)構(gòu),在出現(xiàn)了某個(gè)包或者類(lèi)的多個(gè)版本時(shí),應(yīng)特別注意。
掌握和靈活運(yùn)用Java的類(lèi)裝載器的體系結(jié)構(gòu),對(duì)程序的系統(tǒng)設(shè)計(jì),程序的實(shí)現(xiàn),已經(jīng)程序的調(diào)試,都有相當(dāng)大的幫助。希望以上的內(nèi)容能夠?qū)δ兴鶐椭?/FONT>
還有,因?yàn)檠芯縯omcat的源碼才研究jvm的,因?yàn)閠omcat里一開(kāi)始就是classloader的應(yīng)用。
目前為止我對(duì)java的WEB項(xiàng)目有兩點(diǎn)很大的疑團(tuán),一:多個(gè)用戶(hù)怎樣運(yùn)行于一個(gè)WEB項(xiàng)目上;二:context的意思,我似懂非懂,表面上像懂了,細(xì)一思考又很迷惑,不知道它確切的說(shuō)是什么東西。有知道的朋友告訴我一下哈,好了,大家請(qǐng)看文章........
Java中類(lèi)的查找與裝載出現(xiàn)的問(wèn)題總是會(huì)時(shí)不時(shí)出現(xiàn)在Java程序員面前,這并不是什么丟臉的事情,相信沒(méi)有一個(gè)Java程序員沒(méi)遇到過(guò)ClassNotException,因此不要為被人瞅見(jiàn)自己也犯這樣的錯(cuò)誤而覺(jué)得不自然,但是在如果出現(xiàn)了ClassNotFoundException后異常后一臉的茫然,那我想你該了解一下java的類(lèi)裝載的體制了,同時(shí)為了進(jìn)行下面的關(guān)于類(lèi)裝載器之間的隔離性的討論,我們先簡(jiǎn)單介紹一下類(lèi)裝載的體系結(jié)構(gòu)。
1. Java類(lèi)裝載體系結(jié)構(gòu)
裝載類(lèi)的過(guò)程非常簡(jiǎn)單:查找類(lèi)所在位置,并將找到的Java類(lèi)的字節(jié)碼裝入內(nèi)存,生成對(duì)應(yīng)的Class對(duì)象。Java的類(lèi)裝載器專(zhuān)門(mén)用來(lái)實(shí)現(xiàn)這樣的過(guò)程,JVM并不止有一個(gè)類(lèi)裝載器,事實(shí)上,如果你愿意的話,你可以讓JVM擁有無(wú)數(shù)個(gè)類(lèi)裝載器,當(dāng)然這除了測(cè)試JVM外,我想不出還有其他的用途。你應(yīng)該已經(jīng)發(fā)現(xiàn)到了這樣一個(gè)問(wèn)題,類(lèi)裝載器自身也是一個(gè)類(lèi),它也需要被裝載到內(nèi)存中來(lái),那么這些類(lèi)裝載器由誰(shuí)來(lái)裝載呢,總得有個(gè)根吧?沒(méi)錯(cuò),確實(shí)存在這樣的根,它就是神龍見(jiàn)首不見(jiàn)尾的Bootstrap ClassLoader. 為什么說(shuō)它神龍見(jiàn)首不見(jiàn)尾呢,因?yàn)槟愀緹o(wú)法在Java代碼中抓住哪怕是它的一點(diǎn)點(diǎn)的尾巴,盡管你能時(shí)時(shí)刻刻體會(huì)到它的存在,因?yàn)閖ava的運(yùn)行環(huán)境所需要的所有類(lèi)庫(kù),都由它來(lái)裝載,而它本身是C++寫(xiě)的程序,可以獨(dú)立運(yùn)行,可以說(shuō)是JVM的運(yùn)行起點(diǎn),偉大吧。在Bootstrap完成它的任務(wù)后,會(huì)生成一個(gè)AppClassLoader(實(shí)際上之前系統(tǒng)還會(huì)使用擴(kuò)展類(lèi)裝載器ExtClassLoader,它用于裝載Java運(yùn)行環(huán)境擴(kuò)展包中的類(lèi)),這個(gè)類(lèi)裝載器才是我們經(jīng)常使用的,可以調(diào)用ClassLoader.getSystemClassLoader() 來(lái)獲得,我們假定程序中沒(méi)有使用類(lèi)裝載器相關(guān)操作設(shè)定或者自定義新的類(lèi)裝載器,那么我們編寫(xiě)的所有java類(lèi)通通會(huì)由它來(lái)裝載,值得尊敬吧。AppClassLoader查找類(lèi)的區(qū)域就是耳熟能詳?shù)腃lasspath,也是初學(xué)者必須跨過(guò)的門(mén)檻,有沒(méi)有靈光一閃的感覺(jué),我們按照它的類(lèi)查找范圍給它取名為類(lèi)路徑類(lèi)裝載器。還是先前假定的情況,當(dāng)Java中出現(xiàn)新的類(lèi),AppClassLoader首先在類(lèi)傳遞給它的父類(lèi)類(lèi)裝載器,也就是Extion ClassLoader,詢(xún)問(wèn)它是否能夠裝載該類(lèi),如果能,那AppClassLoader就不干這活了,同樣Extion ClassLoader在裝載時(shí),也會(huì)先問(wèn)問(wèn)它的父類(lèi)裝載器。我們可以看出類(lèi)裝載器實(shí)際上是一個(gè)樹(shù)狀的結(jié)構(gòu)圖,每個(gè)類(lèi)裝載器有自己的父親,類(lèi)裝載器在裝載類(lèi)時(shí),總是先讓自己的父類(lèi)裝載器裝載(多么尊敬長(zhǎng)輩),如果父類(lèi)裝載器無(wú)法裝載該類(lèi)時(shí),自己就會(huì)動(dòng)手裝載,如果它也裝載不了,那么對(duì)不起,它會(huì)大喊一聲:Exception,class not found。有必要提一句,當(dāng)由直接使用類(lèi)路徑裝載器裝載類(lèi)失敗拋出的是NoClassDefFoundException異常。如果使用自定義的類(lèi)裝載器loadClass方法或者ClassLoader的findSystemClass方法裝載類(lèi),如果你不去刻意改變,那么拋出的是ClassNotFoundException。
我們簡(jiǎn)短總結(jié)一下上面的討論:
1.JVM類(lèi)裝載器的體系結(jié)構(gòu)可以看作是樹(shù)狀結(jié)構(gòu)。
2.父類(lèi)裝載器優(yōu)先裝載。在父類(lèi)裝載器裝載失敗的情況下再裝載,如果都裝載失敗則拋出ClassNotFoundException或者NoClassDefFoundError異常。
那么我們的類(lèi)在什么情況下被裝載的呢?
2. 類(lèi)如何被裝載
在java2中,JVM是如何裝載類(lèi)的呢,可以分為兩種類(lèi)型,一種是隱式的類(lèi)裝載,一種式顯式的類(lèi)裝載。
2.1 隱式的類(lèi)裝載
隱式的類(lèi)裝載是編碼中最常用得方式:
A b = new A();
如果程序運(yùn)行到這段代碼時(shí)還沒(méi)有A類(lèi),那么JVM會(huì)請(qǐng)求裝載當(dāng)前類(lèi)的類(lèi)裝器來(lái)裝載類(lèi)。問(wèn)題來(lái)了,我把代碼弄得復(fù)雜一點(diǎn)點(diǎn),但依舊沒(méi)有任何難度,請(qǐng)思考JVM得裝載次序:
package test;
Public class A{
public void static main(String args[]){
B b = new B();
}
}
class B{C c;}
class C{}
揭曉答案,類(lèi)裝載的次序?yàn)锳->B,而類(lèi)C根本不會(huì)被JVM理會(huì),先不要驚訝,仔細(xì)想想,這不正是我們最需要得到的結(jié)果。我們仔細(xì)了解一下JVM裝載順序。當(dāng)使用Java A命令運(yùn)行A類(lèi)時(shí),JVM會(huì)首先要求類(lèi)路徑類(lèi)裝載器(AppClassLoader)裝載A類(lèi),但是這時(shí)只裝載A,不會(huì)裝載A中出現(xiàn)的其他類(lèi)(B類(lèi)),接著它會(huì)調(diào)用A中的main函數(shù),直到運(yùn)行語(yǔ)句b = new B()時(shí),JVM發(fā)現(xiàn)必須裝載B類(lèi)程序才能繼續(xù)運(yùn)行,于是類(lèi)路徑類(lèi)裝載器會(huì)去裝載B類(lèi),雖然我們可以看到B中有有C類(lèi)的聲明,但是并不是實(shí)際的執(zhí)行語(yǔ)句,所以并不去裝載C類(lèi),也就是說(shuō)JVM按照運(yùn)行時(shí)的有效執(zhí)行語(yǔ)句,來(lái)決定是否需要裝載新類(lèi),從而裝載盡可能少的類(lèi),這一點(diǎn)和編譯類(lèi)是不相同的。
2.2 顯式的類(lèi)裝載
使用顯示的類(lèi)裝載方法很多,我們都裝載類(lèi)test.A為例。
使用Class類(lèi)的forName方法。它可以指定裝載器,也可以使用裝載當(dāng)前類(lèi)的裝載器。例如:
Class.forName("test.A");
它的效果和
Class.forName("test.A",true,this.getClass().getClassLoader());
是一樣的。
使用類(lèi)路徑類(lèi)裝載裝載.
ClassLoader.getSystemClassLoader().loadClass("test.A");
使用當(dāng)前進(jìn)程上下文的使用的類(lèi)裝載器進(jìn)行裝載,這種裝載類(lèi)的方法常常被有著復(fù)雜類(lèi)裝載體系結(jié)構(gòu)的系統(tǒng)所使用。
Thread.currentThread().getContextClassLoader().loadClass("test.A")
使用自定義的類(lèi)裝載器裝載類(lèi)
public class MyClassLoader extends URLClassLoader{
public MyClassLoader() {
super(new URL[0]);
}
}
MyClassLoader myClassLoader = new MyClassLoader();
myClassLoader.loadClass("test.A");
MyClassLoader繼承了URLClassLoader類(lèi),這是JDK核心包中的類(lèi)裝載器,在沒(méi)有指定父類(lèi)裝載器的情況下,類(lèi)路徑類(lèi)裝載器就是它的父類(lèi)裝載器,MyClassLoader并沒(méi)有增加類(lèi)的查找范圍,因此它和類(lèi)路徑裝載器有相同的效果。
我們已經(jīng)知道Java的類(lèi)裝載器體系結(jié)構(gòu)為樹(shù)狀,多個(gè)類(lèi)裝載器可以指定同一個(gè)類(lèi)裝載器作為自己的父類(lèi),每個(gè)子類(lèi)裝載器就是樹(shù)狀結(jié)構(gòu)的一個(gè)分支,當(dāng)然它們又可以個(gè)有子類(lèi)裝載器類(lèi)裝載器,類(lèi)裝載器也可以沒(méi)有父類(lèi)裝載器,這時(shí)Bootstrap類(lèi)裝載器將作為它的隱含父類(lèi),實(shí)際上Bootstrap類(lèi)裝載器是所有類(lèi)裝載器的祖先,也是樹(shù)狀結(jié)構(gòu)的根。這種樹(shù)狀體系結(jié)構(gòu),以及父類(lèi)裝載器優(yōu)先的機(jī)制,為我們編寫(xiě)自定義的類(lèi)裝載器提供了便利,同時(shí)可以讓程序按照我們希望的方式進(jìn)行類(lèi)的裝載。例如某個(gè)程序的類(lèi)裝載器體系結(jié)構(gòu)圖如下:
此處沒(méi)找到圖,見(jiàn)諒
圖2:某個(gè)程序的類(lèi)裝載器的結(jié)構(gòu)
解釋一下上面的圖,ClassLoaderA為自定義的類(lèi)裝載器,它的父類(lèi)裝載器為類(lèi)路徑裝載器,它有兩個(gè)子類(lèi)裝載器ClassLoaderAA和ClassLaderAB,ClassLoaderB為程序使用的另外一個(gè)類(lèi)裝載器,它沒(méi)有父類(lèi)裝載器,但有一個(gè)子類(lèi)裝載器ClassLoaderBB。你可能會(huì)說(shuō),見(jiàn)鬼,我的程序怎么會(huì)使用這么復(fù)雜的類(lèi)裝載器結(jié)構(gòu)。為了進(jìn)行下面的討論,暫且委屈一下。
3. 奇怪的隔離性
我們不難發(fā)現(xiàn),圖2中的類(lèi)裝載器AA和AB, AB和BB,AA和B等等位于不同分支下,他們之間沒(méi)有父子關(guān)系,我不知道如何定義這種關(guān)系,姑且稱(chēng)他們位于不同分支下。兩個(gè)位于不同分支的類(lèi)裝載器具有隔離性,這種隔離性使得在分別使用它們裝載同一個(gè)類(lèi),也會(huì)在內(nèi)存中出現(xiàn)兩個(gè)Class類(lèi)的實(shí)例。因?yàn)楸痪哂懈綦x性的類(lèi)裝載器裝載的類(lèi)不會(huì)共享內(nèi)存空間,使得使用一個(gè)類(lèi)裝載器不可能完成的任務(wù)變得可以輕而易舉,例如類(lèi)的靜態(tài)變量可能同時(shí)擁有多個(gè)值(雖然好像作用不大),因?yàn)榫退闶潜谎b載類(lèi)的同一靜態(tài)變量,它們也將被保存不同的內(nèi)存空間,又例如程序需要使用某些包,但又不希望被程序另外一些包所使用,很簡(jiǎn)單,編寫(xiě)自定義的類(lèi)裝載器。類(lèi)裝載器的這種隔離性在許多大型的軟件應(yīng)用和服務(wù)程序得到了很好的應(yīng)用。下面是同一個(gè)類(lèi)靜態(tài)變量為不同值的例子。
package test;
public class A {
public static void main( String[] args ) {
try {
//定義兩個(gè)類(lèi)裝載器
MyClassLoader aa= new MyClassLoader();
MyClassLoader bb = new MyClassLoader();
//用類(lèi)裝載器aa裝載testb.B類(lèi)
Class clazz=aa.loadClass("testb. B");
Constructor constructor=
clazz.getConstructor(new Class[]{Integer.class});
Object object =
constructor.newInstance(new Object[]{new Integer(1)});
Method method =
clazz.getDeclaredMethod("printB",new Class[0]);
//用類(lèi)裝載器bb裝載testb.B類(lèi)
Class clazz2=bb.loadClass("testb. B");
Constructor constructor2 =
clazz2.getConstructor(new Class[]{Integer.class});
Object object2 =
constructor2.newInstance(new Object[]{new Integer(2)});
Method method2 =
clazz2.getDeclaredMethod("printB",new Class[0]);
//顯示test.B中的靜態(tài)變量的值
method.invoke( object,new Object[0]);
method2.invoke( object2,new Object[0]);
} catch ( Exception e ) {
e.printStackTrace();
}
}
}
//Class B 必須位于MyClassLoader的查找范圍內(nèi),
//而不應(yīng)該在MyClassLoader的父類(lèi)裝載器的查找范圍內(nèi)。
package testb;
public class B {
static int b ;
public B(Integer testb) {
b = testb.intValue();
}
public void printB() {
System.out.print("my static field b is ", b);
}
}
public class MyClassLoader extends URLClassLoader{
private static File file = new File("c:\\classes ");
//該路徑存放著class B,但是沒(méi)有class A
public MyClassLoader() {
super(getUrl());
}
public static URL[] getUrl() {
try {
return new URL[]{file.toURL()};
} catch ( MalformedURLException e ) {
return new URL[0];
}
}
}
程序的運(yùn)行結(jié)果為:
my static field b is 1
my static field b is 2
程序的結(jié)果非常有意思,從編程者的角度,我們甚至可以把不在同一個(gè)分支的類(lèi)裝載器看作不同的java虛擬機(jī),因?yàn)樗鼈儽舜擞X(jué)察不到對(duì)方的存在。程序在使用具有分支的類(lèi)裝載的體系結(jié)構(gòu)時(shí)要非常小心,弄清楚每個(gè)類(lèi)裝載器的類(lèi)查找范圍,盡量避免父類(lèi)裝載器和子類(lèi)裝載器的類(lèi)查找范圍中有相同類(lèi)名的類(lèi)(包括包名和類(lèi)名),下面這個(gè)例子就是用來(lái)說(shuō)明這種情況可能帶來(lái)的問(wèn)題。
假設(shè)有相同名字卻不同版本的接口 A,
版本 1:
package test;
Intefer Same{ public String getVersion(); }
版本 2:
Package test;
Intefer Same{ public String getName(); }
接口A兩個(gè)版本的實(shí)現(xiàn):
版本1的實(shí)現(xiàn)
package test;
public class Same1Impl implements Same {
public String getVersion(){ return "A version 1";}
}
版本2的實(shí)現(xiàn)
public class Same 2Impl implements Same {
public String getName(){ return "A version 2";}
}
我們依然使用圖2的類(lèi)裝載器結(jié)構(gòu),首先將版本1的Same和Same的實(shí)現(xiàn)類(lèi)Same1Impl打成包same1.jar,將版本2的Same和Same的實(shí)現(xiàn)類(lèi)Same1Impl打成包same2.jar。現(xiàn)在,做這樣的事情,把same1.jar放入類(lèi)裝載器ClassLoaderA的類(lèi)查找范圍中,把same2.jar放入類(lèi)裝器ClassLoaderAB的類(lèi)查找范圍中。當(dāng)你興沖沖的運(yùn)行下面這個(gè)看似正確的程序。
實(shí)際上這個(gè)錯(cuò)誤的是由父類(lèi)載器優(yōu)先裝載的機(jī)制造成,當(dāng)類(lèi)裝載器ClassLoaderAB在裝載Same2Impl類(lèi)時(shí)發(fā)現(xiàn)必須裝載接口test.Same,于是按規(guī)定請(qǐng)求父類(lèi)裝載器裝載,父類(lèi)裝載器發(fā)現(xiàn)了版本1的test.Same接口并興沖沖的裝載,但是卻想不到Same2Impl所希望的是版本2 的test.Same,后面的事情可想而知了,異常被拋出。
我們很難責(zé)怪Java中暫時(shí)并沒(méi)有提供區(qū)分版本的機(jī)制,如果使用了比較復(fù)雜的類(lèi)裝載器體系結(jié)構(gòu),在出現(xiàn)了某個(gè)包或者類(lèi)的多個(gè)版本時(shí),應(yīng)特別注意。
掌握和靈活運(yùn)用Java的類(lèi)裝載器的體系結(jié)構(gòu),對(duì)程序的系統(tǒng)設(shè)計(jì),程序的實(shí)現(xiàn),已經(jīng)程序的調(diào)試,都有相當(dāng)大的幫助。希望以上的內(nèi)容能夠?qū)δ兴鶐椭?/FONT>