欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入解析Java中的Class Loader類加載器

 更新時(shí)間:2016年03月18日 08:59:11   作者:Radic_Feng  
這篇文章主要介紹了Java中的類加載器,是Java入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下

類加載的過程
類加載器的主要工作就是把類文件加載到JVM中。如下圖所示,其過程分為三步:

1.加載:定位要加載的類文件,并將其字節(jié)流裝載到JVM中;
2.鏈接:給要加載的類分配最基本的內(nèi)存結(jié)構(gòu)保存其信息,比如屬性,方法以及引用的類。在該階段,該類還處于不可用狀態(tài);
(1)驗(yàn)證:對加載的字節(jié)流進(jìn)行驗(yàn)證,比如格式上的,安全方面的;
(2)內(nèi)存分配:為該類準(zhǔn)備內(nèi)存空間來表示其屬性,方法以及引用的類;
(3)解析:加載該類所引用的其它類,比如父類,實(shí)現(xiàn)的接口等。
3.初始化:對類變量進(jìn)行賦值。

201631885518621.gif (550×287)
類加載器的層級
下圖虛線以上是JDK提供的幾個(gè)重要的類加載器,詳細(xì)說明如下:

(1)Bootstrap Class Loader: 當(dāng)啟動(dòng)包含主函數(shù)的類時(shí),加載JAVA_HOME/lib目錄下或-Xbootclasspath指定目錄的jar包;
(2)Extention Class Loader:加載JAVA_HOME/lib/ext目錄下的或-Djava.ext.dirs指定目錄下的jar包。
(3)System Class Loader:加載classpath或者-Djava.class.path指定目錄下的類或jar包。

201631885610971.gif (334×406)

需要了解的是:

1.除了Bootstrap Class Loader外,其它的類加載器都是java.lang.ClassLoader類的子類;
2.Bootstrap Class Loader不是用Java實(shí)現(xiàn),如果你沒有使用個(gè)性化類加載器,那么java.lang.String.class.getClassLoader()就為null,Extension Class Loader的父加載器也為null;
3.獲得類加載器的幾種方式:
(1)獲得Bootstrap Class Loader:試圖獲得Bootstrap Class Loader,得到的必然是null。可以用如下方式驗(yàn)證下:使用rt.jar包內(nèi)的類對象的getClassLoader方法,比如java.lang.String.class.getClassLoader()可以得到或者獲得Extention Class Loader,再調(diào)用getParent方法獲得;
(2)獲得Extention Class Loader:使用JAVA_HOME/lib/ext目錄下jar包內(nèi)的類對象的getClassLoader方法或者先獲得System Class Loader,再通過它的getParent方法獲得;
(3)獲得System Class Loader:調(diào)用包含主函數(shù)的類對象的getClassLoader方法或者在主函數(shù)內(nèi)調(diào)用Thread.currentThread().getContextClassLoader()或者調(diào)用ClassLoader.getSystemClassLoader();
(4)獲得User-Defined Class Loader:調(diào)用類對象的getClassLoader方法或者調(diào)用Thread.currentThread().getContextClassLoader();

類加載器的操作原則
1.代理原則
2.可見性原則
3.唯一性原則
4.代理原則
代理原則指的是一個(gè)類加載器在加載一個(gè)類時(shí)會(huì)請求它的父加載器代理加載,父加載器也會(huì)請求它的父加載器代理加載,如下圖所示。

201631885639380.gif (279×306)

為什么要使用代理模式呢?首先這樣可以減少重復(fù)的加載一個(gè)類。(還有其它原因嗎?)

容易誤解的地方:

一般會(huì)以為類加載器的代理順序是Parent First的,也就是:

1.加載一個(gè)類時(shí),類加載器首先檢查自己是否已經(jīng)加載了該類,如果已加載,則返回;否則請父加載器代理;
2.父加載器重復(fù)1的操作一直到Bootstrap Class Loader;
3.如果Bootstrap Class Loader也沒有加載該類,將嘗試進(jìn)行加載,加載成功則返回;如果失敗,拋出ClassNotFoundException,則由子加載器進(jìn)行加載;
4.子類加載器捕捉異常后嘗試加載,如果成功則返回,如果失敗則拋出ClassNotFoundException,直到發(fā)起加載的子類加載器。
這種理解對Bootstrap Class Loader,Extention Class Loader,System Class Loader這些加載器是正確的,但一些個(gè)性化的加載器則不然,比如,IBM Web Sphere Portal Server實(shí)現(xiàn)的一些類加載器就是Parent Last的,是子加載器首先嘗試加載,如果加載失敗才會(huì)請父加載器,這樣做的原因是:假如你期望某個(gè)版本log4j被所有應(yīng)用使用,就把它放在WAS_HOME的庫里,WAS啟動(dòng)時(shí)會(huì)加載它。如果某個(gè)應(yīng)用想使用另外一個(gè)版本的log4j,如果使用Parent First,這是無法實(shí)現(xiàn)的,因?yàn)楦讣虞d器里已經(jīng)加載了log4j內(nèi)的類。但如果使用Parent Last,負(fù)責(zé)加載應(yīng)用的類加載器會(huì)優(yōu)先加載另外一個(gè)版本的log4j。

可見性原則
每個(gè)類對類加載器的可見性是不一樣的,如下圖所示。

擴(kuò)展知識,OSGi就是利用這個(gè)特點(diǎn),每一個(gè)bundle由一個(gè)單獨(dú)的類加載器加載,因此每個(gè)類加載器都可以加載某個(gè)類的一個(gè)版本,因此整個(gè)系統(tǒng)就可以使用一個(gè)類的多個(gè)版本。

201631891143204.gif (333×365)

唯一性原則
每一個(gè)類在一個(gè)加載器里最多加載一次。

擴(kuò)展知識1:準(zhǔn)確地講,Singleton模式所指的單例指的是在一組類加載器中某個(gè)類的對象只有一份。

擴(kuò)展知識2:一個(gè)類可以被多個(gè)類加載器加載,每個(gè)類對象在各自的namespace內(nèi),對類對象進(jìn)行比較或者對實(shí)例進(jìn)行類型轉(zhuǎn)換時(shí),會(huì)同時(shí)比較各自的名字空間,比如:

Klass類被ClassLoaderA加載,假設(shè)類對象為KlassA;同時(shí)被ClassLoaderB加載,假設(shè)類對象為KlassB,那么KlassA不等于KlassB。同時(shí)ClassA的實(shí)例被cast成KlassB時(shí)會(huì)拋出ClassCastException異常。
為什么要個(gè)性化類加載器
個(gè)性化類加載器給Java語言增加了很多靈活性,主要的用途有:

1.可以從多個(gè)地方加載類,比如網(wǎng)絡(luò)上,數(shù)據(jù)庫中,甚至即時(shí)的編譯源文件獲得類文件;
2.個(gè)性化后類加載器可以在運(yùn)行時(shí)原則性的加載某個(gè)版本的類文件;
3.個(gè)性化后類加載器可以動(dòng)態(tài)卸載一些類;
4.個(gè)性化后類加載器可以對類進(jìn)行解密解壓縮后再載入類。


類的隱式和顯式加載
隱式加載:當(dāng)一個(gè)類被引用,被繼承或者被實(shí)例化時(shí)會(huì)被隱式加載,如果加載失敗,是拋出NoClassDefFoundError。

顯式加載:使用如下方法,如果加載失敗會(huì)拋出ClassNotFoundException。

cl.loadClass(),cl是類加載器的一個(gè)實(shí)例;
Class.forName(),使用當(dāng)前類的類加載器進(jìn)行加載。
類的靜態(tài)塊的執(zhí)行
假如有如下類:

package cn.fengd; 
 
public class Dummy { 
 static { 
 System.out.println("Hi"); 
 } 
} 

另建一個(gè)測試類:

package cn.fengd; 
 
public class ClassLoaderTest { 
 
 public static void main(String[] args) throws InstantiationException, Exception { 
 
 try { 
  /* 
  * Different ways of loading. 
  */ 
  Class c = ClassLoaderTest.class.getClassLoader().loadClass("cn.fengd.Dummy"); 
 } catch (Exception e) { 
  // TODO Auto-generated catch block 
  e.printStackTrace(); 
 } 
 } 
 
} 

運(yùn)行后效果如何呢?

  • 不會(huì)輸出Hi。由此可見使用loadClass后Class類對象并沒有初始化;
  • 如果在Load語句后加上c.newInstance(); 就會(huì)有Hi輸出,對該類進(jìn)行實(shí)例化時(shí)才初始化類對象。
  • 如果換一種加載語句Class c = Class.forName("cn.fengd.Dummy", false, ClassLoader.getSystemClassLoader());
  • 不會(huì)輸出Hi。因?yàn)閰?shù)false表示不需要初始化該類對象;
  • 如果在Load語句后加上c.newInstance(); 就會(huì)有Hi輸出,對該類進(jìn)行實(shí)例化時(shí)才初始化類對象。

如果換成Class.forName("cn.fengd.Dummy");或者new Dummy()呢?

都會(huì)輸出Hi。

常見問題分析:

1.由不同的類加載器加載的指定類型還是相同的類型嗎?
在Java中,一個(gè)類用其完全匹配類名(fully qualified class name)作為標(biāo)識,這里指的完全匹配類名包括包名和類名。但在JVM中一個(gè)類用其全名和一個(gè)加載類ClassLoader的實(shí)例作為唯一標(biāo)識,不同類加載器加載的類將被置于不同的命名空間.我們可以用兩個(gè)自定義類加載器去加載某自定義類型(注意,不要將自定義類型的字節(jié)碼放置到系統(tǒng)路徑或者擴(kuò)展路徑中,否則會(huì)被系統(tǒng)類加載器或擴(kuò)展類加載器搶先加載),然后用獲取到的兩個(gè)Class實(shí)例進(jìn)行java.lang.Object.equals(…)判斷,將會(huì)得到不相等的結(jié)果。這個(gè)大家可以寫兩個(gè)自定義的類加載器去加載相同的自定義類型,然后做個(gè)判斷;同時(shí),可以測試加載java.*類型,然后再對比測試一下測試結(jié)果。

2.在代碼中直接調(diào)用Class.forName(String name)方法,到底會(huì)觸發(fā)那個(gè)類加載器進(jìn)行類加載行為?
Class.forName(String name)默認(rèn)會(huì)使用調(diào)用類的類加載器來進(jìn)行類加載。我們直接來分析一下對應(yīng)的jdk的代碼:

//java.lang.Class.java

 publicstatic Class<?> forName(String className)throws ClassNotFoundException {

return forName0(className, true, ClassLoader.getCallerClassLoader());

}

//java.lang.ClassLoader.java

// Returns the invoker's class loader, or null if none.

static ClassLoader getCallerClassLoader() {

  // 獲取調(diào)用類(caller)的類型

 Class caller = Reflection.getCallerClass(3);

  // This can be null if the VM is requesting it

 if (caller == null) {

  returnnull;

 }

 // 調(diào)用java.lang.Class中本地方法獲取加載該調(diào)用類(caller)的ClassLoader

 return caller.getClassLoader0();

}

//java.lang.Class.java

//虛擬機(jī)本地實(shí)現(xiàn),獲取當(dāng)前類的類加載器
native ClassLoader getClassLoader0();

3.在編寫自定義類加載器時(shí),如果沒有設(shè)定父加載器,那么父加載器是?
在不指定父類加載器的情況下,默認(rèn)采用系統(tǒng)類加載器??赡苡腥擞X得不明白,現(xiàn)在我們來看一下JDK對應(yīng)的代碼實(shí)現(xiàn)。眾所周知,我們編寫自定義的類加載器直接或者間接繼承自java.lang.ClassLoader抽象類,對應(yīng)的無參默認(rèn)構(gòu)造函數(shù)實(shí)現(xiàn)如下:

//摘自java.lang.ClassLoader.java
protected ClassLoader() {

  SecurityManager security = System.getSecurityManager();

  if (security != null) {

  security.checkCreateClassLoader();

  }

  this.parent = getSystemClassLoader();

  initialized = true;

}

我們再來看一下對應(yīng)的getSystemClassLoader()方法的實(shí)現(xiàn):

privatestaticsynchronizedvoid initSystemClassLoader() {

  //...

  sun.misc.Launcher l = sun.misc.Launcher.getLauncher();

  scl = l.getClassLoader();

  //...

}

我們可以寫簡單的測試代碼來測試一下:

System.out.println(sun.misc.Launcher.getLauncher().getClassLoader());

本機(jī)對應(yīng)輸出如下:

sun.misc.Launcher$AppClassLoader@197d257

所以,我們現(xiàn)在可以相信當(dāng)自定義類加載器沒有指定父類加載器的情況下,默認(rèn)的父類加載器即為系統(tǒng)類加載器。同時(shí),我們可以得出如下結(jié)論:

即時(shí)用戶自定義類加載器不指定父類加載器,那么,同樣可以加載如下三個(gè)地方的類:

(1)<Java_Runtime_Home>/lib下的類

(2)< Java_Runtime_Home >/lib/ext下或者由系統(tǒng)變量java.ext.dir指定位置中的類

(3)當(dāng)前工程類路徑下或者由系統(tǒng)變量java.class.path指定位置中的類

4.在編寫自定義類加載器時(shí),如果將父類加載器強(qiáng)制設(shè)置為null,那么會(huì)有什么影響?如果自定義的類加載器不能加載指定類,就肯定會(huì)加載失敗嗎?
JVM規(guī)范中規(guī)定如果用戶自定義的類加載器將父類加載器強(qiáng)制設(shè)置為null,那么會(huì)自動(dòng)將啟動(dòng)類加載器設(shè)置為當(dāng)前用戶自定義類加載器的父類加載器(這個(gè)問題前面已經(jīng)分析過了)。同時(shí),我們可以得出如下結(jié)論:
即時(shí)用戶自定義類加載器不指定父類加載器,那么,同樣可以加載到<Java_Runtime_Home>/lib下的類,但此時(shí)就不能夠加載<Java_Runtime_Home>/lib/ext目錄下的類了。
    說明:問題3和問題4的推斷結(jié)論是基于用戶自定義的類加載器本身延續(xù)了java.lang.ClassLoader.loadClass(…)默認(rèn)委派邏輯,如果用戶對這一默認(rèn)委派邏輯進(jìn)行了改變,以上推斷結(jié)論就不一定成立了,詳見問題5。

5.編寫自定義類加載器時(shí),一般有哪些注意點(diǎn)?
(1)一般盡量不要覆寫已有的loadClass(…)方法中的委派邏輯
一般在JDK 1.2之前的版本才這樣做,而且事實(shí)證明,這樣做極有可能引起系統(tǒng)默認(rèn)的類加載器不能正常工作。在JVM規(guī)范和JDK文檔中(1.2或者以后版本中),都沒有建議用戶覆寫loadClass(…)方法,相比而言,明確提示開發(fā)者在開發(fā)自定義的類加載器時(shí)覆寫findClass(…)邏輯。舉一個(gè)例子來驗(yàn)證該問題:

//用戶自定義類加載器WrongClassLoader.Java(覆寫loadClass邏輯)
publicclassWrongClassLoaderextends ClassLoader {

 public Class<?> loadClass(String name) throws ClassNotFoundException {

  returnthis.findClass(name);

 }

 protected Class<?> findClass(String name) throws ClassNotFoundException {

  //假設(shè)此處只是到工程以外的特定目錄D:/library下去加載類

  具體實(shí)現(xiàn)代碼省略

 }

}

    通過前面的分析我們已經(jīng)知道,用戶自定義類加載器(WrongClassLoader)的默

       認(rèn)的類加載器是系統(tǒng)類加載器,但是現(xiàn)在問題4種的結(jié)論就不成立了。大家可以簡

       單測試一下,現(xiàn)在<Java_Runtime_Home>/lib、< Java_Runtime_Home >/lib/ext和工

       程類路徑上的類都加載不上了。

問題5測試代碼一

publicclass WrongClassLoaderTest {

 publicstaticvoid main(String[] args) {

  try {

  WrongClassLoader loader = new WrongClassLoader();

  Class classLoaded = loader.loadClass("beans.Account");

  System.out.println(classLoaded.getName());

  System.out.println(classLoaded.getClassLoader());

  } catch (Exception e) {

  e.printStackTrace();

  }

 }

}

(說明:D:"classes"beans"Account.class物理存在的)

輸出結(jié)果:

java.io.FileNotFoundException: D:"classes"java"lang"Object.class (系統(tǒng)找不到指定的路徑。)

 at java.io.FileInputStream.open(Native Method)

 at java.io.FileInputStream.<init>(FileInputStream.java:106)

 at WrongClassLoader.findClass(WrongClassLoader.java:40)

 at WrongClassLoader.loadClass(WrongClassLoader.java:29)

 at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)

 at java.lang.ClassLoader.defineClass1(Native Method)

 at java.lang.ClassLoader.defineClass(ClassLoader.java:620)

 at java.lang.ClassLoader.defineClass(ClassLoader.java:400)

 at WrongClassLoader.findClass(WrongClassLoader.java:43)

 at WrongClassLoader.loadClass(WrongClassLoader.java:29)

 at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)

Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object

 at java.lang.ClassLoader.defineClass1(Native Method)

 at java.lang.ClassLoader.defineClass(ClassLoader.java:620)

 at java.lang.ClassLoader.defineClass(ClassLoader.java:400)

 at WrongClassLoader.findClass(WrongClassLoader.java:43)

 at WrongClassLoader.loadClass(WrongClassLoader.java:29)

 at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)

這說明,連要加載的類型的超類型java.lang.Object都加載不到了。這里列舉的由于覆寫loadClass(…)引起的邏輯錯(cuò)誤明顯是比較簡單的,實(shí)際引起的邏輯錯(cuò)誤可能復(fù)雜的多。
問題5測試二

//用戶自定義類加載器WrongClassLoader.Java(不覆寫loadClass邏輯)
publicclassWrongClassLoaderextends ClassLoader {

 protected Class<?> findClass(String name) throws ClassNotFoundException {

  //假設(shè)此處只是到工程以外的特定目錄D:/library下去加載類

  具體實(shí)現(xiàn)代碼省略

 }

}

將自定義類加載器代碼WrongClassLoader.Java做以上修改后,再運(yùn)行測試代碼,輸出結(jié)果如下:

beans.Account

WrongClassLoader@1c78e57

這說明,beans.Account加載成功,且是由自定義類加載器WrongClassLoader加載。

這其中的原因分析,我想這里就不必解釋了,大家應(yīng)該可以分析的出來了。

(2)正確設(shè)置父類加載器
通過上面問題4和問題5的分析我們應(yīng)該已經(jīng)理解,個(gè)人覺得這是自定義用戶類加載器時(shí)最重要的一點(diǎn),但常常被忽略或者輕易帶過。有了前面JDK代碼的分析作為基礎(chǔ),我想現(xiàn)在大家都可以隨便舉出例子了。
(3)保證findClass(String )方法的邏輯正確性
事先盡量準(zhǔn)確理解待定義的類加載器要完成的加載任務(wù),確保最大程度上能夠獲取到對應(yīng)的字節(jié)碼內(nèi)容。

6.如何在運(yùn)行時(shí)判斷系統(tǒng)類加載器能加載哪些路徑下的類?
一是可以直接調(diào)用ClassLoader.getSystemClassLoader()或者其他方式獲取到系統(tǒng)類加載器(系統(tǒng)類加載器和擴(kuò)展類加載器本身都派生自URLClassLoader),調(diào)用URLClassLoader中的getURLs()方法可以獲取到;

二是可以直接通過獲取系統(tǒng)屬性java.class.path 來查看當(dāng)前類路徑上的條目信息 , System.getProperty("java.class.path")

7.如何在運(yùn)行時(shí)判斷標(biāo)準(zhǔn)擴(kuò)展類加載器能加載哪些路徑下的類?
方法之一:

try {
  URL[] extURLs = ((URLClassLoader)ClassLoader.getSystemClassLoader().getParent()).getURLs();

  for (int i = 0; i < extURLs.length; i++) {

   System.out.println(extURLs[i]);

  }

 } catch (Exception e) {//…}

       本機(jī)對應(yīng)輸出如下:

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/dnsns.jar

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/localedata.jar

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jar

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar

相關(guān)文章

  • java中charAt()方法的使用及說明

    java中charAt()方法的使用及說明

    這篇文章主要介紹了java中charAt()方法的使用及說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Java操作redis實(shí)現(xiàn)增刪查改功能的方法示例

    Java操作redis實(shí)現(xiàn)增刪查改功能的方法示例

    這篇文章主要介紹了Java操作redis實(shí)現(xiàn)增刪查改功能的方法,涉及java操作redis數(shù)據(jù)庫的連接、設(shè)置、增刪改查、釋放資源等相關(guān)操作技巧,需要的朋友可以參考下
    2017-08-08
  • JavaEE通過response實(shí)現(xiàn)請求重定向

    JavaEE通過response實(shí)現(xiàn)請求重定向

    這篇文章主要介紹了JavaEE通過response實(shí)現(xiàn)請求重定向的方法,非常的簡單實(shí)用,有需要的朋友可以參考下
    2014-10-10
  • 解決FontConfiguration.getVersion報(bào)空指針異常的問題

    解決FontConfiguration.getVersion報(bào)空指針異常的問題

    這篇文章主要介紹了解決FontConfiguration.getVersion報(bào)空指針異常的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 微服務(wù)中使用Maven BOM來管理你的版本依賴詳解

    微服務(wù)中使用Maven BOM來管理你的版本依賴詳解

    這篇文章主要介紹了微服務(wù)中使用Maven BOM來管理你的版本依賴,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • idea中如何連接hive

    idea中如何連接hive

    這篇文章主要介紹了idea中如何連接hive問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Java零基礎(chǔ)講解異常

    Java零基礎(chǔ)講解異常

    異常就是不正常,比如當(dāng)我們身體出現(xiàn)了異常我們會(huì)根據(jù)身體情況選擇喝開水、吃藥、看病、等?異常處理方法。?java異常處理機(jī)制是我們java語言使用異常處理機(jī)制為程序提供了錯(cuò)誤處理的能力,程序出現(xiàn)的錯(cuò)誤,程序可以安全的退出,以保證程序正常的運(yùn)行等
    2022-04-04
  • Java實(shí)現(xiàn)在Word指定位置插入分頁符

    Java實(shí)現(xiàn)在Word指定位置插入分頁符

    在Word插入分頁符可以在指定段落后插入,也可以在特定文本位置處插入。本文將以Java代碼來操作以上兩種文檔分頁需求,需要的可以參考一下
    2022-04-04
  • java 使用線程監(jiān)控文件目錄變化的實(shí)現(xiàn)方法

    java 使用線程監(jiān)控文件目錄變化的實(shí)現(xiàn)方法

    這篇文章主要介紹了java 使用線程監(jiān)控文件目錄變化的實(shí)現(xiàn)方法的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-10-10
  • Spring boot實(shí)現(xiàn)上傳文件到本地服務(wù)器

    Spring boot實(shí)現(xiàn)上傳文件到本地服務(wù)器

    這篇文章主要為大家詳細(xì)介紹了Spring boot實(shí)現(xiàn)上傳文件到本地服務(wù)器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-08-08

最新評論