一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

類加載機(jī)制(類加載過程和類加載器)

 gaoshenmu 2016-09-11

一、為什么要使用類加載器?
Java語(yǔ)言里,類加載都是在程序運(yùn)行期間完成的,這種策略雖然會(huì)令類加載時(shí)稍微增加一些性能開銷,但是會(huì)給java應(yīng)用程序提供高度的靈活性。例如:
1.編寫一個(gè)面向接口的應(yīng)用程序,可能等到運(yùn)行時(shí)再指定其實(shí)現(xiàn)的子類;
2.用戶可以自定義一個(gè)類加載器,讓程序在運(yùn)行時(shí)從網(wǎng)絡(luò)或其他地方加載一個(gè)二進(jìn)制流作為程序代碼的一部分;(這個(gè)是Android插件化,動(dòng)態(tài)安裝更新apk的基礎(chǔ))

 

二、類加載的過程

使用java編譯器可以把java代碼編譯為存儲(chǔ)字節(jié)碼的Class文件,使用其他語(yǔ)言的編譯器一樣可以把程序代碼翻譯成Class文件,java虛擬機(jī)不關(guān)心Class的來源是何種語(yǔ)言。如圖所示:

在Class文件中描述的各種信息,最終都需要加載到虛擬機(jī)中才能運(yùn)行和使用。那么虛擬機(jī)是如何加載這些Class文件的呢?
JVM把描述類數(shù)據(jù)的字節(jié)碼.Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的java類型,這就是虛擬機(jī)的類加載機(jī)制。

 

類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止,它的生命周期包括了:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸載(Unloading)七個(gè)階段,其中驗(yàn)證、準(zhǔn)備、解析三個(gè)部分統(tǒng)稱鏈接。


加載(裝載)、驗(yàn)證、準(zhǔn)備、初始化和卸載這五個(gè)階段順序是固定的,類的加載過程必須按照這種順序開始,而解析階段不一定;它在某些情況下可以在初始化之后再開始,這是為了運(yùn)行時(shí)動(dòng)態(tài)綁定特性(JIT例如接口只在調(diào)用的時(shí)候才知道具體實(shí)現(xiàn)的是哪個(gè)子類)。值得注意的是:這些階段通常都是互相交叉的混合式進(jìn)行的,通常會(huì)在一個(gè)階段執(zhí)行的過程中調(diào)用或激活另外一個(gè)階段。

 

1.加載:(重點(diǎn))
加載階段是“類加載機(jī)制”中的一個(gè)階段,這個(gè)階段通常也被稱作“裝載”,主要完成:
1.通過“類全名”來獲取定義此類的二進(jìn)制字節(jié)流

2.將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)

3.在java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這些數(shù)據(jù)的訪問入口

相對(duì)于類加載過程的其他階段,加載階段(準(zhǔn)備地說,是加載階段中獲取類的二進(jìn)制字節(jié)流的動(dòng)作)是開發(fā)期可控性最強(qiáng)的階段,因?yàn)榧虞d階段可以使用系統(tǒng)提供的類加載器(ClassLoader)來完成,也可以由用戶自定義的類加載器完成,開發(fā)人員可以通過定義自己的類加載器去控制字節(jié)流的獲取方式。

加載階段完成后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)之中,方法區(qū)中的數(shù)據(jù)存儲(chǔ)格式有虛擬機(jī)實(shí)現(xiàn)自行定義,虛擬機(jī)并未規(guī)定此區(qū)域的具體數(shù)據(jù)結(jié)構(gòu)。然后在java堆中實(shí)例化一個(gè)java.lang.Class類的對(duì)象,這個(gè)對(duì)象作為程序訪問方法區(qū)中的這些類型數(shù)據(jù)的外部接口。

 

2.驗(yàn)證:(了解)

驗(yàn)證是鏈接階段的第一步,這一步主要的目的是確保class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身安全。
驗(yàn)證階段主要包括四個(gè)檢驗(yàn)過程:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證。

1.文件格式驗(yàn)證

 驗(yàn)證class文件格式規(guī)范,例如: class文件是否已魔術(shù)0xCAFEBABE開頭 , 主、次版本號(hào)是否在當(dāng)前虛擬機(jī)處理范圍之內(nèi)等

2.元數(shù)據(jù)驗(yàn)證

這個(gè)階段是對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,以保證起描述的信息符合java語(yǔ)言規(guī)范要求。驗(yàn)證點(diǎn)可能包括:這個(gè)類是否有父類(除了java.lang.Object之外,所有的類都應(yīng)當(dāng)有父類)、這個(gè)類是否繼承了不允許被繼承的類(被final修飾的)、如果這個(gè)類的父類是抽象類,是否實(shí)現(xiàn)了起父類或接口中要求實(shí)現(xiàn)的所有方法。

3.字節(jié)碼驗(yàn)證

 進(jìn)行數(shù)據(jù)流和控制流分析,這個(gè)階段對(duì)類的方法體進(jìn)行校驗(yàn)分析,這個(gè)階段的任務(wù)是保證被校驗(yàn)類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的行為。如:保證訪法體中的類型轉(zhuǎn)換有效,例如可以把一個(gè)子類對(duì)象賦值給父類數(shù)據(jù)類型,這是安全的,但不能把一個(gè)父類對(duì)象賦值給子類數(shù)據(jù)類型、保證跳轉(zhuǎn)命令不會(huì)跳轉(zhuǎn)到方法體以外的字節(jié)碼命令上。

4.符號(hào)引用驗(yàn)證

符號(hào)引用中通過字符串描述的全限定名是否能找到對(duì)應(yīng)的類、符號(hào)引用類中的類,字段和方法的訪問性(private、protected、public、default)是否可被當(dāng)前類訪問。

3.準(zhǔn)備:(了解)

準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。這個(gè)階段中有兩個(gè)容易產(chǎn)生混淆的知識(shí)點(diǎn),首先是這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static 修飾的變量),而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在java堆中。其次是這里所說的初始值“通常情況”下是數(shù)據(jù)類型的零值,假設(shè)一個(gè)類變量定義為:

public static int value  = 12;

那么變量value在準(zhǔn)備階段過后的初始值為0而不是12,因?yàn)檫@時(shí)候尚未開始執(zhí)行任何java方法,而把value賦值為123的putstatic指令是程序被編譯后,存放于類構(gòu)造器()方法之中,所以把value賦值為12的動(dòng)作將在初始化階段才會(huì)被執(zhí)行。

上面所說的“通常情況”下初始值是零值,那相對(duì)于一些特殊的情況,如果類字段的字段屬性表中存在ConstantValue屬性,那在準(zhǔn)備階段變量value就會(huì)被初始化為ConstantValue屬性所指定的值,建設(shè)上面類變量value定義為:

public static final int value = 123;

編譯時(shí)javac將會(huì)為value生成ConstantValue屬性,在準(zhǔn)備階段虛擬機(jī)就會(huì)根據(jù)ConstantValue的設(shè)置將value設(shè)置為123。

 

4.解析:(了解)
解析階段是虛擬機(jī)常量池內(nèi)的符號(hào)引用替換為直接引用的過程。
符號(hào)引用:符號(hào)引用是一組符號(hào)來描述所引用的目標(biāo)對(duì)象,符號(hào)可以是任何形式的字面量,只要使用時(shí)能無歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)對(duì)象并不一定已經(jīng)加載到內(nèi)存中。

直接引用:直接引用可以是直接指向目標(biāo)對(duì)象的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。直接引用是與虛擬機(jī)內(nèi)存布局實(shí)現(xiàn)相關(guān)的,同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來的直接引用一般不會(huì)相同,如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。

虛擬機(jī)規(guī)范并沒有規(guī)定解析階段發(fā)生的具體時(shí)間,只要求了在執(zhí)行anewarry、checkcast、getfield、instanceof、invokeinterface、invokespecial、invokestatic、invokevirtual、multianewarray、new、putfield和putstatic這13個(gè)用于操作符號(hào)引用的字節(jié)碼指令之前,先對(duì)它們使用的符號(hào)引用進(jìn)行解析,所以虛擬機(jī)實(shí)現(xiàn)會(huì)根據(jù)需要來判斷,到底是在類被加載器加載時(shí)就對(duì)常量池中的符號(hào)引用進(jìn)行解析,還是等到一個(gè)符號(hào)引用將要被使用前才去解析它。

解析的動(dòng)作主要針對(duì)類或接口、字段、類方法、接口方法四類符號(hào)引用進(jìn)行。分別對(duì)應(yīng)編譯后常量池內(nèi)的CONSTANT_Class_Info、CONSTANT_Fieldref_Info、CONSTANT_Methodef_Info、CONSTANT_InterfaceMethoder_Info四種常量類型。

1.類、接口的解析

2.字段解析

3.類方法解析

4.接口方法解析

 

5.初始化:(了解)

類的初始化階段是類加載過程的最后一步,在準(zhǔn)備階段,類變量已賦過一次系統(tǒng)要求的初始值,而在初始化階段,則是根據(jù)程序員通過程序制定的主觀計(jì)劃去初始化類變量和其他資源,或者可以從另外一個(gè)角度來表達(dá):初始化階段是執(zhí)行類構(gòu)造器()方法的過程。在以下四種情況下初始化過程會(huì)被觸發(fā)執(zhí)行:

1.遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則需先觸發(fā)其初始化。生成這4條指令的最常見的java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用類的靜態(tài)方法的時(shí)候。

2.使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候

3.當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化、則需要先出發(fā)其父類的初始化

4.jvm啟動(dòng)時(shí),用戶指定一個(gè)執(zhí)行的主類(包含main方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)類

在上面準(zhǔn)備階段 public static int value  = 12;  在準(zhǔn)備階段完成后 value的值為0,而在初始化階調(diào)用了類構(gòu)造器()方法,這個(gè)階段完成后value的值為12。

*類構(gòu)造器()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static塊)中的語(yǔ)句合并產(chǎn)生的,編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語(yǔ)句塊中只能訪問到定義在靜態(tài)語(yǔ)句塊之前的變量,定義在它之后的變量,在前面的靜態(tài)語(yǔ)句快可以賦值,但是不能訪問。

*類構(gòu)造器()方法與類的構(gòu)造函數(shù)(實(shí)例構(gòu)造函數(shù)()方法)不同,它不需要顯式調(diào)用父類構(gòu)造,虛擬機(jī)會(huì)保證在子類()方法執(zhí)行之前,父類的()方法已經(jīng)執(zhí)行完畢。因此在虛擬機(jī)中的第一個(gè)執(zhí)行的()方法的類肯定是java.lang.Object。

*由于父類的()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語(yǔ)句快要優(yōu)先于子類的變量賦值操作。

*()方法對(duì)于類或接口來說并不是必須的,如果一個(gè)類中沒有靜態(tài)語(yǔ)句,也沒有變量賦值的操作,那么編譯器可以不為這個(gè)類生成()方法。

*接口中不能使用靜態(tài)語(yǔ)句塊,但接口與類不太能夠的是,執(zhí)行接口的()方法不需要先執(zhí)行父接口的()方法。只有當(dāng)父接口中定義的變量被使用時(shí),父接口才會(huì)被初始化。另外,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會(huì)執(zhí)行接口的()方法。

*虛擬機(jī)會(huì)保證一個(gè)類的()方法在多線程環(huán)境中被正確加鎖和同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)線程執(zhí)行這個(gè)類的()方法,其他線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行()方法完畢。如果一個(gè)類的()方法中有耗時(shí)很長(zhǎng)的操作,那就可能造成多個(gè)進(jìn)程阻塞。

 

 

三、類加載器

JVM設(shè)計(jì)者把類加載階段中的“通過'類全名'來獲取定義此類的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作放到Java虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需要的類。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為“類加載器”。

 

1.類與類加載器

對(duì)于任何一個(gè)類,都需要由加載它的類加載器和這個(gè)類來確立其在JVM中的唯一性。也就是說,兩個(gè)類來源于同一個(gè)Class文件,并且被同一個(gè)類加載器加載,這兩個(gè)類才相等。

2.雙親委派模型

從虛擬機(jī)的角度來說,只存在兩種不同的類加載器:一種是啟動(dòng)類加載器(Bootstrap ClassLoader),該類加載器使用C++語(yǔ)言實(shí)現(xiàn),屬于虛擬機(jī)自身的一部分。另外一種就是所有其它的類加載器,這些類加載器是由Java語(yǔ)言實(shí)現(xiàn),獨(dú)立于JVM外部,并且全部繼承自抽象類java.lang.ClassLoader。

 

從Java開發(fā)人員的角度來看,大部分Java程序一般會(huì)使用到以下三種系統(tǒng)提供的類加載器:
1)啟動(dòng)類加載器(Bootstrap ClassLoader):負(fù)責(zé)加載JAVA_HOME\lib目錄中并且能被虛擬機(jī)識(shí)別的類庫(kù)到JVM內(nèi)存中,如果名稱不符合的類庫(kù)即使放在lib目錄中也不會(huì)被加載。該類加載器無法被Java程序直接引用。
2)擴(kuò)展類加載器(Extension ClassLoader):該加載器主要是負(fù)責(zé)加載JAVA_HOME\lib\,該加載器可以被開發(fā)者直接使用。
3)應(yīng)用程序類加載器(Application ClassLoader):該類加載器也稱為系統(tǒng)類加載器,它負(fù)責(zé)加載用戶類路徑(Classpath)上所指定的類庫(kù),開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。

我們的應(yīng)用程序都是由這三類加載器互相配合進(jìn)行加載的,我們也可以加入自己定義的類加載器。這些類加載器之間的關(guān)系如下圖所示:

如上圖所示的類加載器之間的這種層次關(guān)系,就稱為類加載器的雙親委派模型(Parent Delegation Model)。該模型要求除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。子類加載器和父類加載器不是以繼承(Inheritance)的關(guān)系來實(shí)現(xiàn),而是通過組合(Composition)關(guān)系來復(fù)用父加載器的代碼。


雙親委派模型的工作過程為:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的加載器都是如此,因此所有的類加載請(qǐng)求都會(huì)傳給頂層的啟動(dòng)類加載器,只有當(dāng)父加載器反饋?zhàn)约簾o法完成該加載請(qǐng)求(該加載器的搜索范圍中沒有找到對(duì)應(yīng)的類)時(shí),子加載器才會(huì)嘗試自己去加載。


使用這種模型來組織類加載器之間的關(guān)系的好處是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如java.lang.Object類,無論哪個(gè)類加載器去加載該類,最終都是由啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。否則的話,如果不使用該模型的話,如果用戶自定義一個(gè)java.lang.Object類且存放在classpath中,那么系統(tǒng)中將會(huì)出現(xiàn)多個(gè)Object類,應(yīng)用程序也會(huì)變得很混亂。如果我們自定義一個(gè)rt.jar中已有類的同名Java類,會(huì)發(fā)現(xiàn)JVM可以正常編譯,但該類永遠(yuǎn)無法被加載運(yùn)行。
在rt.jar包中的java.lang.ClassLoader類中,我們可以查看類加載實(shí)現(xiàn)過程的代碼,具體源碼如下:

[java] view plain copy
  1. protected synchronized Class loadClass(String name, boolean resolve)  
  2.         throws ClassNotFoundException {  
  3.     // 首先檢查該name指定的class是否有被加載  
  4.     Class c = findLoadedClass(name);  
  5.     if (c == null) {  
  6.         try {  
  7.             if (parent != null) {  
  8.                 // 如果parent不為null,則調(diào)用parent的loadClass進(jìn)行加載  
  9.                 c = parent.loadClass(name, false);  
  10.             } else {  
  11.                 // parent為null,則調(diào)用BootstrapClassLoader進(jìn)行加載  
  12.                 c = findBootstrapClass0(name);  
  13.             }  
  14.         } catch (ClassNotFoundException e) {  
  15.             // 如果仍然無法加載成功,則調(diào)用自身的findClass進(jìn)行加載  
  16.             c = findClass(name);  
  17.         }  
  18.     }  
  19.     if (resolve) {  
  20.         resolveClass(c);  
  21.     }  
  22.     return c;  
  23. }  

通過上面代碼可以看出,雙親委派模型是通過loadClass()方法來實(shí)現(xiàn)的,根據(jù)代碼以及代碼中的注釋可以很清楚地了解整個(gè)過程其實(shí)非常簡(jiǎn)單:先檢查是否已經(jīng)被加載過,如果沒有則調(diào)用父加載器的loadClass()方法,如果父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器。如果父類加載器加載失敗,則先拋出ClassNotFoundException,然后再調(diào)用自己的findClass()方法進(jìn)行加載。

 

3.自定義類加載器

若要實(shí)現(xiàn)自定義類加載器,只需要繼承java.lang.ClassLoader 類,并且重寫其findClass()方法即可。java.lang.ClassLoader 類的基本職責(zé)就是根據(jù)一個(gè)指定的類的名稱,找到或者生成其對(duì)應(yīng)的字節(jié)代碼,然后從這些字節(jié)代碼中定義出一個(gè) Java 類,即 java.lang.Class 類的一個(gè)實(shí)例。除此之外,ClassLoader 還負(fù)責(zé)加載 Java 應(yīng)用所需的資源,如圖像文件和配置文件等,ClassLoader 中與加載類相關(guān)的方法如下:

 
方法                                 說明
getParent()  返回該類加載器的父類加載器。

loadClass(String name) 加載名稱為 二進(jìn)制名稱為name 的類,返回的結(jié)果是 java.lang.Class 類的實(shí)例。

findClass(String name) 查找名稱為 name 的類,返回的結(jié)果是 java.lang.Class 類的實(shí)例。

findLoadedClass(String name) 查找名稱為 name 的已經(jīng)被加載過的類,返回的結(jié)果是 java.lang.Class 類的實(shí)例。

resolveClass(Class c) 鏈接指定的 Java 類。


注意:在JDK1.2之前,類加載尚未引入雙親委派模式,因此實(shí)現(xiàn)自定義類加載器時(shí)常常重寫loadClass方法,提供雙親委派邏輯,從JDK1.2之后,雙親委派模式已經(jīng)被引入到類加載體系中,自定義類加載器時(shí)不需要在自己寫雙親委派的邏輯,因此不鼓勵(lì)重寫loadClass方法,而推薦重寫findClass方法。

在Java中,任意一個(gè)類都需要由加載它的類加載器和這個(gè)類本身一同確定其在java虛擬機(jī)中的唯一性,即比較兩個(gè)類是否相等,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提之下才有意義,否則,即使這兩個(gè)類來源于同一個(gè)Class類文件,只要加載它的類加載器不相同,那么這兩個(gè)類必定不相等(這里的相等包括代表類的Class對(duì)象的equals()方法、isAssignableFrom()方法、isInstance()方法和instanceof關(guān)鍵字的結(jié)果)。例子代碼如下:

[java] view plain copy
  1. /** 
  2.      * 一、ClassLoader加載類的順序 
  3.      *  1.調(diào)用 findLoadedClass(String) 來檢查是否已經(jīng)加載類。 
  4.      *  2.在父類加載器上調(diào)用 loadClass 方法。如果父類加載器為 null,則使用虛擬機(jī)的內(nèi)置類加載器。 
  5.      *  3.調(diào)用 findClass(String) 方法查找類。 
  6.      * 二、實(shí)現(xiàn)自己的類加載器 
  7.      *  1.獲取類的class文件的字節(jié)數(shù)組 
  8.      *  2.將字節(jié)數(shù)組轉(zhuǎn)換為Class類的實(shí)例 
  9.      * @author lei 2011-9-1 
  10.      */  
  11.     public class ClassLoaderTest {  
  12.         public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {  
  13.             //新建一個(gè)類加載器  
  14.             MyClassLoader cl = new MyClassLoader('myClassLoader');  
  15.             //加載類,得到Class對(duì)象  
  16.             Class clazz = cl.loadClass('classloader.Animal');  
  17.             //得到類的實(shí)例  
  18.             Animal animal=(Animal) clazz.newInstance();  
  19.             animal.say();  
  20.         }  
  21.     }  
  22.     class Animal{  
  23.         public void say(){  
  24.             System.out.println('hello world!');  
  25.         }  
  26.     }  
  27.     class MyClassLoader extends ClassLoader {  
  28.         //類加載器的名稱  
  29.         private String name;  
  30.         //類存放的路徑  
  31.         private String path = 'E:\\workspace\\Algorithm\\src';  
  32.         MyClassLoader(String name) {  
  33.             this.name = name;  
  34.         }  
  35.         MyClassLoader(ClassLoader parent, String name) {  
  36.             super(parent);  
  37.             this.name = name;  
  38.         }  
  39.         /** 
  40.          * 重寫findClass方法 
  41.          */  
  42.         @Override  
  43.         public Class findClass(String name) {  
  44.             byte[] data = loadClassData(name);  
  45.             return this.defineClass(name, data, 0, data.length);  
  46.         }  
  47.         public byte[] loadClassData(String name) {  
  48.             try {  
  49.                 name = name.replace('.''//');  
  50.                 FileInputStream is = new FileInputStream(new File(path + name + '.class'));  
  51.                 ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  52.                 int b = 0;  
  53.                 while ((b = is.read()) != -1) {  
  54.                     baos.write(b);  
  55.                 }  
  56.                 return baos.toByteArray();  
  57.             } catch (Exception e) {  
  58.                 e.printStackTrace();  
  59.             }  
  60.             return null;  
  61.         }  
  62.     }  

 

類加載器雙親委派模型是從JDK1.2以后引入的,并且只是一種推薦的模型,不是強(qiáng)制要求的,因此有一些沒有遵循雙親委派模型的特例:(了解)

(1).在JDK1.2之前,自定義類加載器都要覆蓋loadClass方法去實(shí)現(xiàn)加載類的功能,JDK1.2引入雙親委派模型之后,loadClass方法用于委派父類加載器進(jìn)行類加載,只有父類加載器無法完成類加載請(qǐng)求時(shí)才調(diào)用自己的findClass方法進(jìn)行類加載,因此在JDK1.2之前的類加載的loadClass方法沒有遵循雙親委派模型,因此在JDK1.2之后,自定義類加載器不推薦覆蓋loadClass方法,而只需要覆蓋findClass方法即可。

(2).雙親委派模式很好地解決了各個(gè)類加載器的基礎(chǔ)類統(tǒng)一問題,越基礎(chǔ)的類由越上層的類加載器進(jìn)行加載,但是這個(gè)基礎(chǔ)類統(tǒng)一有一個(gè)不足,當(dāng)基礎(chǔ)類想要調(diào)用回下層的用戶代碼時(shí)無法委派子類加載器進(jìn)行類加載。為了解決這個(gè)問題JDK引入了ThreadContext線程上下文,通過線程上下文的setContextClassLoader方法可以設(shè)置線程上下文類加載器。

JavaEE只是一個(gè)規(guī)范,sun公司只給出了接口規(guī)范,具體的實(shí)現(xiàn)由各個(gè)廠商進(jìn)行實(shí)現(xiàn),因此JNDI,JDBC,JAXB等這些第三方的實(shí)現(xiàn)庫(kù)就可以被JDK的類庫(kù)所調(diào)用。線程上下文類加載器也沒有遵循雙親委派模型。

(3).近年來的熱碼替換,模塊熱部署等應(yīng)用要求不用重啟java虛擬機(jī)就可以實(shí)現(xiàn)代碼模塊的即插即用,催生了OSGi技術(shù),在OSGi中類加載器體系被發(fā)展為網(wǎng)狀結(jié)構(gòu)。OSGi也沒有完全遵循雙親委派模型。

4.動(dòng)態(tài)加載Jar && ClassLoader 隔離問題

動(dòng)態(tài)加載Jar:

Java 中動(dòng)態(tài)加載 Jar 比較簡(jiǎn)單,如下:

[java] view plain copy
  1. URL[] urls = new URL[] {new URL('file:libs/jar1.jar')};  
  2. URLClassLoader loader = new URLClassLoader(urls, parentLoader);  

表示加載 libs 下面的 jar1.jar,其中 parentLoader 就是上面1中的 parent,可以為當(dāng)前的 ClassLoader。


ClassLoader 隔離問題:

大家覺得一個(gè)運(yùn)行程序中有沒有可能同時(shí)存在兩個(gè)包名和類名完全一致的類?
JVM 及 Dalvik 對(duì)類唯一的識(shí)別是 ClassLoader id + PackageName + ClassName,所以一個(gè)運(yùn)行程序中是有可能存在兩個(gè)包名和類名完全一致的類的。并且如果這兩個(gè)”類”不是由一個(gè) ClassLoader 加載,是無法將一個(gè)類的示例強(qiáng)轉(zhuǎn)為另外一個(gè)類的,這就是 ClassLoader 隔離。 如 Android 中碰到如下異常

[java] view plain copy
  1. android.support.v4.view.ViewPager can not be cast to android.support.v4.view.ViewPager  

當(dāng)碰到這種問題時(shí)可以通過 instance.getClass().getClassLoader(); 得到 ClassLoader,看 ClassLoader 是否一樣。

 

加載不同 Jar 包中公共類:

現(xiàn)在 Host 工程包含了 common.jar, jar1.jar, jar2.jar,并且 jar1.jar 和 jar2.jar 都包含了 common.jar,我們通過 ClassLoader 將 jar1, jar2 動(dòng)態(tài)加載進(jìn)來,這樣在 Host 中實(shí)際是存在三份 common.jar,如下圖:

https://farm4./3872/14301963930_2f0f0fe8aa_o.png

我們?cè)趺幢WC common.jar 只有一份而不會(huì)造成上面3中提到的 ClassLoader 隔離的問題呢,其實(shí)很簡(jiǎn)單,在生成 jar1 和 jar2 時(shí)把 common.jar 去掉,只保留 host 中一份,以 host ClassLoader 為 parentClassLoader 即可。

 

最后:

一道面試題

能不能自己寫個(gè)類叫java.lang.System?

答案:通常不可以,但可以采取另類方法達(dá)到這個(gè)需求。 
解釋:為了不讓我們寫System類,類加載采用委托機(jī)制,這樣可以保證爸爸們優(yōu)先,爸爸們能找到的類,兒子就沒有機(jī)會(huì)加載。而System類是Bootstrap加載器加載的,就算自己重寫,也總是使用Java系統(tǒng)提供的System,自己寫的System類根本沒有機(jī)會(huì)得到加載。

但是,我們可以自己定義一個(gè)類加載器來達(dá)到這個(gè)目的,為了避免雙親委托機(jī)制,這個(gè)類加載器也必須是特殊的。由于系統(tǒng)自帶的三個(gè)類加載器都加載特定目錄下的類,如果我們自己的類加載器放在一個(gè)特殊的目錄,那么系統(tǒng)的加載器就無法加載,也就是最終還是由我們自己的加載器加載。


本文系轉(zhuǎn)載,原文鏈接: http://blog.csdn.net/boyupeng/article/details/47951037

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    九九热在线视频观看最新| 欧美国产日本免费不卡| 日本加勒比在线观看一区| 激情五月激情婷婷丁香| 九九九热视频最新在线| 午夜精品一区二区av| 国产日韩欧美综合视频| 亚洲伦片免费偷拍一区| 欧美胖熟妇一区二区三区| 亚洲一级二级三级精品| 免费在线成人激情视频| 欧美不卡午夜中文字幕| 精品日韩欧美一区久久| 欧美日韩亚洲精品内裤| 午夜久久久精品国产精品| 一区二区欧美另类稀缺| 欧美胖熟妇一区二区三区| 中文字幕日韩欧美一区| 日本精品啪啪一区二区三区| 国产亚洲午夜高清国产拍精品| 亚洲国产精品久久综合网| 日韩中文字幕有码午夜美女| 中文字幕亚洲精品人妻| 91福利视频日本免费看看| 国产一级内射麻豆91| 一区二区三区国产日韩| 国产又粗又猛又大爽又黄| 好吊日成人免费视频公开| 欧美欧美日韩综合一区| 亚洲一区二区精品免费| 日韩中文无线码在线视频| 又黄又色又爽又免费的视频| 日韩免费午夜福利视频| 神马午夜福利一区二区| 欧美日韩久久精品一区二区| 91亚洲精品国产一区| 国产一级内片内射免费看| 日韩丝袜诱惑一区二区| 国产视频一区二区三区四区| 日本欧美一区二区三区高清| 国产日本欧美韩国在线|