程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 深刻解析Java中的Class Loader類加載器

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

編輯:關於JAVA

深刻解析Java中的Class Loader類加載器。本站提示廣大學習愛好者:(深刻解析Java中的Class Loader類加載器)文章只能為提供參考,不一定能成為您想要的結果。以下是深刻解析Java中的Class Loader類加載器正文


類加載的進程
類加載器的重要任務就是把類文件加載到JVM中。以下圖所示,其進程分為三步:

1.加載:定位要加載的類文件,並將其字撙節裝載到JVM中;
2.鏈接:給要加載的類分派最根本的內存構造保留其信息,好比屬性,辦法和援用的類。在該階段,該類還處於弗成用狀況;
(1)驗證:對加載的字撙節停止驗證,好比格局上的,平安方面的;
(2)內存分派:為該類預備內存空間來表現其屬性,辦法和援用的類;
(3)解析:加載該類所援用的其它類,好比父類,完成的接口等。
3.初始化:對類變量停止賦值。


類加載器的層級
下圖虛線以上是JDK供給的幾個主要的類加載器,具體解釋以下:

(1)Bootstrap Class Loader: 當啟動包括主函數的類時,加載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包。

須要懂得的是:

1.除Bootstrap Class Loader外,其它的類加載器都是java.lang.ClassLoader類的子類;
2.Bootstrap Class Loader不是用Java完成,假如你沒有應用特性化類加載器,那末java.lang.String.class.getClassLoader()就為null,Extension Class Loader的父加載器也為null;
3.取得類加載器的幾種方法:
(1)取得Bootstrap Class Loader:試圖取得Bootstrap Class Loader,獲得的必定是null。可以用以下方法驗證下:應用rt.jar包內的類對象的getClassLoader辦法,好比java.lang.String.class.getClassLoader()可以獲得或許取得Extention Class Loader,再挪用getParent辦法取得;
(2)取得Extention Class Loader:應用JAVA_HOME/lib/ext目次下jar包內的類對象的getClassLoader辦法或許先取得System Class Loader,再經由過程它的getParent辦法取得;
(3)取得System Class Loader:挪用包括主函數的類對象的getClassLoader辦法或許在主函數內挪用Thread.currentThread().getContextClassLoader()或許挪用ClassLoader.getSystemClassLoader();
(4)取得User-Defined Class Loader:挪用類對象的getClassLoader辦法或許挪用Thread.currentThread().getContextClassLoader();

類加載器的操作准繩
1.署理准繩
2.可見性准繩
3.獨一性准繩
4.署理准繩
署理准繩指的是一個類加載器在加載一個類時會要求它的父加載器署理加載,父加載器也會要求它的父加載器署理加載,以下圖所示。

為何要應用署理形式呢?起首如許可以削減反復的加載一個類。(還有其它緣由嗎?)

輕易誤會的處所:

普通會認為類加載器的署理次序是Parent First的,也就是:

1.加載一個類時,類加載器起首檢討本身能否曾經加載了該類,假如已加載,則前往;不然請父加載器署理;
2.父加載重視復1的操作一向到Bootstrap Class Loader;
3.假如Bootstrap Class Loader也沒有加載該類,將測驗考試停止加載,加載勝利則前往;假如掉敗,拋出ClassNotFoundException,則由子加載器停止加載;
4.子類加載器捕獲異常後測驗考試加載,假如勝利則前往,假如掉敗則拋出ClassNotFoundException,直到提議加載的子類加載器。
這類懂得對Bootstrap Class Loader,Extention Class Loader,System Class Loader這些加載器是准確的,但一些特性化的加載器則否則,好比,IBM Web Sphere Portal Server完成的一些類加載器就是Parent Last的,是子加載器起首測驗考試加載,假如加載掉敗才會請父加載器,如許做的緣由是:假設你希冀某個版本log4j被一切運用應用,就把它放在WAS_HOME的庫裡,WAS啟動時會加載它。假如某個運用想應用別的一個版本的log4j,假如應用Parent First,這是沒法完成的,由於父加載器裡曾經加載了log4j內的類。但假如應用Parent Last,擔任加載運用的類加載器會優先加載別的一個版本的log4j。

可見性准繩
每一個類對類加載器的可見性是紛歧樣的,以下圖所示。

擴大常識,OSGi就是應用這個特色,每個bundle由一個零丁的類加載器加載,是以每一個類加載器都可以加載某個類的一個版本,是以全部體系便可以應用一個類的多個版本。

獨一性准繩
每個類在一個加載器裡最多加載一次。

擴大常識1:精確地講,Singleton形式所指的單例指的是在一組類加載器中某個類的對象只要一份。

擴大常識2:一個類可以被多個類加載器加載,每一個類對象在各自的namespace內,對類對象停止比擬或許對實例停止類型轉換時,會同時比擬各自的名字空間,好比:

Klass類被ClassLoaderA加載,假定類對象為KlassA;同時被ClassLoaderB加載,假定類對象為KlassB,那末KlassA不等於KlassB。同時ClassA的實例被cast成KlassB時會拋出ClassCastException異常。
為何要特性化類加載器
特性化類加載器給Java說話增長了許多靈巧性,重要的用處有:

1.可以從多個處所加載類,好比收集上,數據庫中,乃至即時的編譯源文件取得類文件;
2.特性化後類加載器可以在運轉時准繩性的加載某個版本的類文件;
3.特性化後類加載器可以靜態卸載一些類;
4.特性化後類加載器可以對類停止解密解緊縮後再載入類。


類的隱式和顯式加載
隱式加載:當一個類被援用,被繼續或許被實例化時會被隱式加載,假如加載掉敗,是拋出NoClassDefFoundError。

顯式加載:應用以下辦法,假如加載掉敗會拋出ClassNotFoundException。

cl.loadClass(),cl是類加載器的一個實例;
Class.forName(),應用以後類的類加載器停止加載。
類的靜態塊的履行
假設有以下類:

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

另建一個測試類:

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(); 
 } 
 } 
 
} 

運轉後後果若何呢?

  • 不會輸入Hi。因而可知應用loadClass後Class類對象並沒有初始化;
  • 假如在Load語句後加上c.newInstance(); 就會有Hi輸入,對該類停止實例化時才初始化類對象。
  • 假如換一種加載語句Class c = Class.forName("cn.fengd.Dummy", false, ClassLoader.getSystemClassLoader());
  • 不會輸入Hi。由於參數false表現不須要初始化該類對象;
  • 假如在Load語句後加上c.newInstance(); 就會有Hi輸入,對該類停止實例化時才初始化類對象。

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

都邑輸入Hi。

罕見成績剖析:

1.由分歧的類加載器加載的指定類型照樣雷同的類型嗎?
在Java中,一個類用其完整婚配類名(fully qualified class name)作為標識,這裡指的完整婚配類名包含包名和類名。但在JVM中一個類用其全名和一個加載類ClassLoader的實例作為獨一標識,分歧類加載器加載的類將被置於分歧的定名空間.我們可以用兩個自界說類加載器去加載某自界說類型(留意,不要將自界說類型的字節碼放置到體系途徑或許擴大途徑中,不然會被體系類加載器或擴大類加載器爭先加載),然後用獲得到的兩個Class實例停止java.lang.Object.equals(…)斷定,將會獲得不相等的成果。這個年夜家可以寫兩個自界說的類加載器去加載雷同的自界說類型,然後做個斷定;同時,可以測試加載java.*類型,然後再比較測試一下測試成果。

2.在代碼中直接挪用Class.forName(String name)辦法,究竟會觸發誰人類加載器停止類加載行動?
Class.forName(String name)默許會應用挪用類的類加載器來停止類加載。我們直接來剖析一下對應的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() {

  // 獲得挪用類(caller)的類型

 Class caller = Reflection.getCallerClass(3);

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

 if (caller == null) {

  returnnull;

 }

 // 挪用java.lang.Class中當地辦法獲得加載該挪用類(caller)的ClassLoader

 return caller.getClassLoader0();

}

//java.lang.Class.java

//虛擬機當地完成,獲得以後類的類加載器
native ClassLoader getClassLoader0();

3.在編寫自界說類加載器時,假如沒有設定父加載器,那末父加載器是?
在不指定父類加載器的情形下,默許采取體系類加載器。能夠有人認為不明確,如今我們來看一下JDK對應的代碼完成。盡人皆知,我們編寫自界說的類加載器直接或許直接繼續自java.lang.ClassLoader籠統類,對應的無參默許結構函數完成以下:

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

  SecurityManager security = System.getSecurityManager();

  if (security != null) {

  security.checkCreateClassLoader();

  }

  this.parent = getSystemClassLoader();

  initialized = true;

}

我們再來看一下對應的getSystemClassLoader()辦法的完成:

privatestaticsynchronizedvoid initSystemClassLoader() {

  //...

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

  scl = l.getClassLoader();

  //...

}

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

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

本機對應輸入以下:

sun.misc.Launcher$AppClassLoader@197d257

所以,我們如今可以信任當自界說類加載器沒有指定父類加載器的情形下,默許的父類加載器即為體系類加載器。同時,我們可以得出以下結論:

即時用戶自界說類加載器不指定父類加載器,那末,異樣可以加載以下三個處所的類:

(1)<Java_Runtime_Home>/lib下的類

(2)< Java_Runtime_Home >/lib/ext下或許由體系變量java.ext.dir指定地位中的類

(3)以後工程類途徑下或許由體系變量java.class.path指定地位中的類

4.在編寫自界說類加載器時,假如將父類加載器強迫設置為null,那末會有甚麼影響?假如自界說的類加載器不克不及加載指定類,就確定會加載掉敗嗎?
JVM標准中劃定假如用戶自界說的類加載器將父類加載器強迫設置為null,那末會主動將啟動類加載器設置為以後用戶自界說類加載器的父類加載器(這個成績後面曾經剖析過了)。同時,我們可以得出以下結論:
即時用戶自界說類加載器不指定父類加載器,那末,異樣可以加載到<Java_Runtime_Home>/lib下的類,但此時就不克不及夠加載<Java_Runtime_Home>/lib/ext目次下的類了。
    解釋:成績3和成績4的揣摸結論是基於用戶自界說的類加載器自己延續了java.lang.ClassLoader.loadClass(…)默許委派邏輯,假如用戶對這一默許委派邏輯停止了轉變,以上揣摸結論就紛歧定成立了,詳見成績5。

5.編寫自界說類加載器時,普通有哪些留意點?
(1)普通盡可能不要覆寫已有的loadClass(…)辦法中的委派邏輯
普通在JDK 1.2之前的版本才如許做,並且現實證實,如許做極有能夠惹起體系默許的類加載器不克不及正常任務。在JVM標准和JDK文檔中(1.2或許今後版本中),都沒有建議用戶覆寫loadClass(…)辦法,比擬而言,明白提醒開辟者在開辟自界說的類加載器時覆寫findClass(…)邏輯。舉一個例子來驗證該成績:

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

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

  returnthis.findClass(name);

 }

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

  //假定此處只是到工程之外的特定目次D:/library下去加載類

  詳細完成代碼省略

 }

}

    經由過程後面的剖析我們曾經曉得,用戶自界說類加載器(WrongClassLoader)的默

       認的類加載器是體系類加載器,然則如今成績4種的結論就不成立了。年夜家可以簡

       單測試一下,如今<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物理存在的)

輸入成果:

java.io.FileNotFoundException: D:"classes"java"lang"Object.class (體系找不到指定的途徑。)

 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(…)惹起的邏輯毛病顯著是比擬簡略的,現實惹起的邏輯毛病能夠龐雜的多。
成績5測試二

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

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

  //假定此處只是到工程之外的特定目次D:/library下去加載類

  詳細完成代碼省略

 }

}

將自界說類加載器代碼WrongClassLoader.Java做以上修正後,再運轉測試代碼,輸入成果以下:

beans.Account

WrongClassLoader@1c78e57

這解釋,beans.Account加載勝利,且是由自界說類加載器WrongClassLoader加載。

這個中的緣由剖析,我想這裡就不用說明了,年夜家應當可以剖析的出來了。

(2)准確設置父類加載器
經由過程下面成績4和成績5的剖析我們應當曾經懂得,小我認為這是自界說用戶類加載器時最主要的一點,但經常被疏忽或許隨意馬虎帶過。有了後面JDK代碼的剖析作為基本,我想如今年夜家都可以隨意舉出例子了。
(3)包管findClass(String )辦法的邏輯准確性
事前盡可能精確懂得待界說的類加載器要完成的加載義務,確保最年夜水平上可以或許獲得到對應的字節碼內容。

6.若何在運轉時斷定體系類加載器能加載哪些途徑下的類?
一是可以直接挪用ClassLoader.getSystemClassLoader()或許其他方法獲得到體系類加載器(體系類加載器和擴大類加載器自己都派生自URLClassLoader),挪用URLClassLoader中的getURLs()辦法可以獲得到;

二是可以直接經由過程獲得體系屬性java.class.path 來檢查以後類途徑上的條目信息 , System.getProperty("java.class.path")

7.若何在運轉時斷定尺度擴大類加載器能加載哪些途徑下的類?
辦法之一:

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

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

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

  }

 } catch (Exception e) {//…}

       本機對應輸入以下:

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved