日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
這一篇文章,可以把Java中的類加載器了解的七七八八了

本文轉(zhuǎn)載自微信公眾號「程序新視界」,作者丑胖俠二師兄。轉(zhuǎn)載本文請聯(lián)系程序新視界公眾號。

成都創(chuàng)新互聯(lián)公司專注于南陽網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供南陽營銷型網(wǎng)站建設(shè),南陽網(wǎng)站制作、南陽網(wǎng)頁設(shè)計(jì)、南陽網(wǎng)站官網(wǎng)定制、微信小程序定制開發(fā)服務(wù),打造南陽網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供南陽網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。

前言

對于每個(gè)開發(fā)人員來說,java.lang.ClassNotFoundExcetpion這個(gè)異常幾乎都遇到過,而追求其該異常的來源的話,就免不了談一談Java的類加載器了。本文就基于啟動類加載器、擴(kuò)展類加載器、系統(tǒng)類加載器和自定義類加載器來為大家補(bǔ)充一下這方面的知識。

類加載器簡介

Java程序被編譯器編譯之后成為字節(jié)碼文件(.class文件),當(dāng)程序需要某個(gè)類時(shí),虛擬機(jī)便會將對應(yīng)的class文件進(jìn)行加載,創(chuàng)建出對應(yīng)的Class對象。而這個(gè)將class文件加載到虛擬機(jī)內(nèi)存的過程,便是類加載。

類加載器負(fù)責(zé)在運(yùn)行時(shí)將Java類動態(tài)加載到JVM(Java虛擬機(jī)),是JRE(Java運(yùn)行時(shí)環(huán)境)的一部分。由于類加載器的存在,JVM無需了解底層文件或文件系統(tǒng)即可運(yùn)行Java程序。

Java類不會一次全部加載到內(nèi)存中,而是在應(yīng)用程序需要時(shí)才會加載。此時(shí),類加載器負(fù)責(zé)將類加載到內(nèi)存中。

類加載的過程

類的生命周期通常包括:加載、鏈接、初始化、使用和卸載。上圖中包含了類加載的三個(gè)階段:加載階段、鏈接階段和初始化階段。如果將這三個(gè)階段再拆分細(xì)化包括:加載、驗(yàn)證、準(zhǔn)備、解析和初始化。

關(guān)于這幾個(gè)階段的作用,已經(jīng)有很多文章在寫了,我們就簡單概況一下:

  • 加載:通過一個(gè)類的完全限定查找類字節(jié)碼文件,轉(zhuǎn)化為方法區(qū)運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu),創(chuàng)建一個(gè)代表該類的Class對象。
  • 驗(yàn)證:確保Class文件的字節(jié)流中包含信息符合當(dāng)前虛擬機(jī)要求,不會危害虛擬機(jī)自身安全。
  • 準(zhǔn)備:為類變量(即static修飾的字段變量)分配內(nèi)存并且設(shè)置該類變量的初始值。不包含被final修飾的static變量,因?yàn)樗诰幾g時(shí)已經(jīng)分配了。
  • 解析:將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程。如果符號引用指向一個(gè)未被加載的類,或者未被加載類的字段或方法,那么解析將觸發(fā)這個(gè)類的加載。
  • 初始化:類加載最后階段,若該類具有超類,則對其進(jìn)行初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化成員變量。

在上述類加載的過程中,虛擬機(jī)內(nèi)部提供了三種類加載器:啟動(Bootstrap)類加載器、擴(kuò)展(Extension)類加載器、系統(tǒng)(System)類加載器(也稱應(yīng)用類加載器)。

下面就討論不同類型的內(nèi)置類加載器是如何工作,以及介紹如何自定義類加載器。

內(nèi)置類加載器

先從一個(gè)簡單的例子來看一下如何使用不同的加載器來加載不同的類:

 
 
 
 
  1. public void printClassLoaders() {
  2.     System.out.println("Classloader of this class:"
  3.         + PrintClassLoader.class.getClassLoader());
  4.     System.out.println("Classloader of Logging:"
  5.         + Logging.class.getClassLoader());
  6.     System.out.println("Classloader of ArrayList:"
  7.         + ArrayList.class.getClassLoader());
  8. }

執(zhí)行上述程序,打印如下內(nèi)容:

 
 
 
 
  1. Classloader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
  2. Classloader of Logging:sun.misc.Launcher$ExtClassLoader@2f0e140b
  3. Classloader of ArrayList:null

上述三行輸出分別對應(yīng)三種不同的類加載器:系統(tǒng)(System)類加載器、擴(kuò)展(Extension)類加載器和啟動(Bootstrap)類加載器(顯示為null)。

系統(tǒng)程序類加載器加載包含示例方法的類,也就是將我們自己的文件加載到類路徑中。擴(kuò)展類加載器加載Logging類,也就是加載作為標(biāo)準(zhǔn)核心Java類擴(kuò)展的類。啟動類加載器加載ArrayList類,是所有其他類的父級。

對于ArrayList的類加載器,輸出為null。這是因?yàn)閱宇惣虞d器是用本機(jī)代碼實(shí)現(xiàn)而不是Java,因此它不會顯示為Java類。啟動類加載器在操作在不同的JVM中會有所不同。

上述三種類加載器,外加自定義類加載器,它們直接的關(guān)系可用下圖表示:

現(xiàn)在來具體看一下這些類加載器。

Bootstrap類加載器

Java類由java.lang.ClassLoader的實(shí)例加載。但是,類加載器本身就是類。那么,誰來加載java.lang.ClassLoader?對,就是啟動類加載器。

啟動類加載器主要負(fù)責(zé)加載JDK內(nèi)部類,通常是rt.jar和$JAVA_HOME/jre/lib目錄中的其他核心庫。此外,Bootstrap類加載器還充當(dāng)所有其他ClassLoader實(shí)例的父類。

該啟動程序類加載器是Java虛擬機(jī)的一部分,用本機(jī)代碼編寫(比如,C++),不同的平臺的實(shí)現(xiàn)可能有所不同。

出于安全考慮,Bootstrap啟動類加載器只加載包名為java、javax、sun等開頭的類。

Extension類加載器

擴(kuò)展類加載器是啟動類加載器的子類,Java語言編寫,由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),父類加載器為啟動類加載器,負(fù)責(zé)加載標(biāo)準(zhǔn)核心Java類的擴(kuò)展。

擴(kuò)展類加載器從JDK擴(kuò)展目錄(通常是$JAVA_HOME/lib/ext目錄)或java.ext.dirs系統(tǒng)屬性中指定的任何其他目錄進(jìn)行自動加載。

系統(tǒng)類加載器

系統(tǒng)類加載器負(fù)責(zé)將所有應(yīng)用程序級類加載到JVM中。它加載在類路徑環(huán)境變量,-classpath或-cp命令行選項(xiàng)中找到的文件。它是擴(kuò)展類加載器的子類。

系統(tǒng)類加載器,也稱應(yīng)用程序加載器是指 Sun公司實(shí)現(xiàn)的sun.misc.Launcher$AppClassLoader,負(fù)責(zé)加載系統(tǒng)類路徑-classpath或-D java.class.path指定路徑下的類庫,也就是我們經(jīng)常用到的classpath路徑,開發(fā)者可以直接使用系統(tǒng)類加載器,一般情況下該類加載是程序中默認(rèn)的類加載器,通過ClassLoader#getSystemClassLoader()方法可以獲取到該類加載器。

類加載器是如何工作的

類加載器是Java運(yùn)行時(shí)環(huán)境的一部分。當(dāng)JVM請求一個(gè)類時(shí),類加載器將嘗試定位該類,并使用完全限定名將類定義裝入運(yùn)行時(shí)。

java.lang.ClassLoader.loadClass()方法負(fù)責(zé)將類定義加載到運(yùn)行時(shí),它嘗試通過全限定名來加載類。如果未加載到該類,則它將請求委派給父類加載器。依次向上重復(fù)該過程。

最終,如果父類加載器找不到指定類,則子類將調(diào)用java.net.URLClassLoader.findClass()方法在文件系統(tǒng)本身中查找類。

如果最后一個(gè)子類加載器也無法加載該類,則它將拋出java.lang.NoClassDefFoundError或java.lang.ClassNotFoundException。拋出ClassNotFoundException時(shí)的輸出示例:

 
 
 
 
  1. java.lang.ClassNotFoundException: com.baeldung.classloader.SampleClassLoader    
  2.     at java.net.URLClassLoader.findClass(URLClassLoader.java:381)    
  3.     at java.lang.ClassLoader.loadClass(ClassLoader.java:424)    
  4.     at java.lang.ClassLoader.loadClass(ClassLoader.java:357)    
  5.     at java.lang.Class.forName0(Native Method)    
  6.     at java.lang.Class.forName(Class.java:348)

上述過程,通常我們稱作雙親委派機(jī)制。雙親委派機(jī)制要求除了頂層的啟動類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器,請注意雙親委派機(jī)制中的父子關(guān)系并非通常所說的類繼承關(guān)系,而是采用組合關(guān)系來復(fù)用父類加載器的相關(guān)代碼。

除外,類加載器還具有三個(gè)重要功能:委派模型、類的唯一性和可見性。

委派模型

類加載器遵循委派模型,在該模型中,根據(jù)請求查找類或資源,ClassLoader實(shí)例會將對類或資源的搜索委托給父類加載器。

假設(shè)我們有一個(gè)將應(yīng)用程序類加載到JVM中的請求。系統(tǒng)類加載器首先將該類的加載委托給其父擴(kuò)展類加載器,而父擴(kuò)展類加載器又將其委托給引導(dǎo)類加載器。

僅當(dāng)啟動類加載器和擴(kuò)展類加載器都未能成功加載類時(shí),系統(tǒng)類加載器才會嘗試加載類本身。

類的唯一性

作為委托模型的結(jié)果,很容易確保類的唯一性,因?yàn)槭冀K嘗試向上委托。如果父類加載器無法找到該類,則只有當(dāng)前實(shí)例自己會嘗試進(jìn)行查找和加載。

可見性

此外,子類加載器對其父類加載器加載的類可見。例如,系統(tǒng)類加載器加載的類對擴(kuò)展和Bootstrap類加載器加載的類具有可見性,反之亦然。

比如,通過系統(tǒng)類加載器加載類A,而通過擴(kuò)展類加載器加載類B,則對系統(tǒng)類加載器加載的其他類而言,A和B類都是可見的。但對擴(kuò)展類加載器加載的其他類而言,類B是唯一可見的類。

自定義類加載器

在大多數(shù)情況下,如果文件已經(jīng)在文件系統(tǒng)中,則內(nèi)置的類加載器就足夠了。但是,在需要從本地硬盤驅(qū)動器或網(wǎng)絡(luò)中加載類的情況下,可能需要使用自定義類加載器。下面介紹自定義類加載器的使用。

自定義類加載器示例

自定義類加載器不僅對在運(yùn)行時(shí)加載類有幫助,還有一些特殊的場景:

  • 幫助修改現(xiàn)有的字節(jié)碼,例如weaving agents;
  • 動態(tài)創(chuàng)建適合用戶需求的類。例如在JDBC中,通過動態(tài)類加載完成不同驅(qū)動程序?qū)崿F(xiàn)之間的切換。
  • 在為具有相同名稱和程序包的類加載不同的字節(jié)碼時(shí),實(shí)現(xiàn)類版本控制機(jī)制。這可以通過URL類加載器(通過URL加載jar)或自定義類加載器來完成。

舉一個(gè)更具體的例子,比如,瀏覽器使用自定義類加載器從網(wǎng)站加載可執(zhí)行內(nèi)容。瀏覽器可以使用單獨(dú)的類加載器從不同的網(wǎng)頁加載applet。用于運(yùn)行applet的applet查看器包含一個(gè)ClassLoader,該類加載器可訪問遠(yuǎn)程服務(wù)器上的網(wǎng)站,而無需查看本地文件系統(tǒng)。

然后通過HTTP加載原始字節(jié)碼文件,并將其轉(zhuǎn)換為JVM中的類。即使這些applet具有相同的名稱,但如果由不同的類加載器加載,它們也被視為不同的組件。

現(xiàn)在,我們了解了為什么自定義類加載器是相關(guān)的,讓我們實(shí)現(xiàn)ClassLoader的子類來擴(kuò)展和總結(jié)JVM如何加載類的。

創(chuàng)建自定義類加載器

自定義類加載器通常通過繼承java.lang.ClassLoader類,重寫findClass()方法:

 
 
 
 
  1. public class CustomClassLoader extends ClassLoader {
  2.     @Override
  3.     public Class findClass(String name) throws ClassNotFoundException {
  4.         byte[] b = loadClassFromFile(name);
  5.         return defineClass(name, b, 0, b.length);
  6.     }
  7.     private byte[] loadClassFromFile(String fileName)  {
  8.         InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
  9.                 fileName.replace('.', File.separatorChar) + ".class");
  10.         byte[] buffer;
  11.         ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
  12.         int nextValue = 0;
  13.         try {
  14.             while ( (nextValue = inputStream.read()) != -1 ) {
  15.                 byteStream.write(nextValue);
  16.             }
  17.         } catch (IOException e) {
  18.             e.printStackTrace();
  19.         }
  20.         buffer = byteStream.toByteArray();
  21.         return buffer;
  22.     }
  23. }

在上面的示例中,我們定義了一個(gè)自定義類加載器,該類加載器擴(kuò)展了默認(rèn)類加載器并從指定文件加載字節(jié)數(shù)組。如果沒有太復(fù)雜的需求,可以直接繼承URLClassLoader類,重寫loadClass方法,具體可參考AppClassLoader和ExtClassLoader。

了解java.lang.ClassLoader

下面來看看java.lang.ClassLoader類中的一些基本方法,以更清楚地了解其工作方式。

loadClass方法

 
 
 
 
  1. public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {

此方法負(fù)責(zé)加載給定名稱參數(shù)的類。name參數(shù)為類的全限定名。

Java虛擬機(jī)調(diào)用loadClass()方法來解析類引用,并將resolve設(shè)置為true。但是,不一定總是要解析一個(gè)類。如果只需要確定該類是否存在,則將resolve參數(shù)設(shè)置為false。

此方法用作類加載器的入口。我們可以嘗試從java.lang.ClassLoader的源代碼中了解loadClass()方法的內(nèi)部工作:

 
 
 
 
  1. protected Class loadClass(String name, boolean resolve)
  2.   throws ClassNotFoundException {
  3.     
  4.     synchronized (getClassLoadingLock(name)) {
  5.         // First, check if the class has already been loaded
  6.         Class c = findLoadedClass(name);
  7.         if (c == null) {
  8.             long t0 = System.nanoTime();
  9.                 try {
  10.                     if (parent != null) {
  11.                         c = parent.loadClass(name, false);
  12.                     } else {
  13.                         c = findBootstrapClassOrNull(name);
  14.                     }
  15.                 } catch (ClassNotFoundException e) {
  16.                     // ClassNotFoundException thrown if class not found
  17.                     // from the non-null parent class loader
  18.                 }
  19.                 if (c == null) {
  20.                     // If still not found, then invoke findClass in order
  21.                     // to find the class.
  22.                     c = findClass(name);
  23.                 }
  24.             }
  25.             if (resolve) {
  26.                 resolveClass(c);
  27.             }
  28.             return c;
  29.         }
  30.     }

該方法的默認(rèn)實(shí)現(xiàn)按以下順序搜索類:

  • 調(diào)用findLoadedClass(String)方法以查看是否已加載該類。
  • 在父類加載器上調(diào)用loadClass(String)方法。
  • 調(diào)用findClass(String)方法以查找類。

defineClass方法

 
 
 
 
  1. protected final Class defineClass(
  2. String name, byte[] b, int off, int len) throws ClassFormatError

此方法負(fù)責(zé)將字節(jié)數(shù)組轉(zhuǎn)換為類的實(shí)例。如果數(shù)據(jù)不包含有效的類,則會拋出ClassFormatError。另外,由于此方法被標(biāo)記為final,因此我們無法覆蓋此方法。

findClass方法

 
 
 
 
  1. protected Class findClass(
  2.   String name) throws ClassNotFoundException

此方法查找以標(biāo)準(zhǔn)名稱作為參數(shù)的類。我們需要在遵循委派模型加載類的自定義類加載器實(shí)現(xiàn)中重寫此方法。

另外,如果父類加載器找不到請求的類,則loadClass()會調(diào)用此方法。如果沒有任何類加載器的父類找到該類,則默認(rèn)實(shí)現(xiàn)會拋出ClassNotFoundException異常。

getParent方法

 
 
 
 
  1. public final ClassLoader getParent()

此方法返回父類加載器以進(jìn)行委派。某些實(shí)現(xiàn)使用null來表示啟動類加載器。

getResource方法

 
 
 
 
  1. public URL getResource(String name)

此方法嘗試查找具有給定名稱的資源。它將首先委托給資源的父類加載器,如果父級為null,則搜索虛擬機(jī)內(nèi)置的類加載器的路徑。如果失敗,則該方法將調(diào)用findResource(String)來查找資源。

指定為輸入的資源名稱可以相對于類路徑,也可以是相對于絕對路徑。

它返回用于讀取資源的URL對象;如果找不到資源或調(diào)用者沒有足夠的特權(quán)來返回資源,則返回null。

需要注意的是,Java是從類路徑中加載資源。

最后,Java中的資源加載被認(rèn)為是與位置無關(guān)的,因?yàn)橹灰O(shè)置了環(huán)境來查找資源,代碼在何處運(yùn)行都無關(guān)緊要。

上下文類加載器

通常,上下文類加載器為J2SE中引入的類加載委托方案提供了一種替代方法。JVM中的類加載器遵循分層模型,因此每個(gè)類加載器都有一個(gè)單獨(dú)的父類,而啟動類加載器除外。但是,有時(shí)當(dāng)JVM核心類需要?jiǎng)討B(tài)加載應(yīng)用程序開發(fā)人員提供的類或資源時(shí),可能會遇到問題。

例如,在JNDI中,核心功能由rt.jar中的引導(dǎo)程序類實(shí)現(xiàn)。但是這些JNDI類可能會加載由獨(dú)立供應(yīng)商實(shí)現(xiàn)的JNDI提供程序(部署在應(yīng)用程序類路徑中)。這種情況要求啟動類加載器(父類加載器)加載對應(yīng)程序加載器(子類加載器)可見的類。

線程上下文類加載器(context class loader)是從JDK 1.2開始引入的。Java.lang.Thread中的方法 getContextClassLoader()和setContextClassLoader(ClassLoader cl)用來獲取和設(shè)置線程的上下文類加載器。如果沒有通過setContextClassLoader(ClassLoader cl)方法進(jìn)行設(shè)置的話,線程將繼承其父線程的上下文類加載器。Java應(yīng)用運(yùn)行的初始線程的上下文類加載器是系統(tǒng)類加載器,在線程中運(yùn)行的代碼可以通過此類加載器來加載類和資源。

線程上下文類加載器從根本解決了一般應(yīng)用不能違背雙親委派模式的問題,使得java類加載體系顯得更靈活。上面所提到的問題正是線程上下文類加載器的拿手好菜。如果不做任何的設(shè)置,Java應(yīng)用的線程上下文類加載器默認(rèn)就是系統(tǒng)類加載器。因此,在SPI接口的代碼中使用線程上下文類加載器,就可以成功的加載到SPI實(shí)現(xiàn)的類。

小結(jié)

類加載器對于執(zhí)行Java程序是必不可少的。我們先學(xué)習(xí)了不同類型的類加載器,即Bootstrap類加載器、擴(kuò)展類加載器和系統(tǒng)類加載器。Bootstrap類加載器充當(dāng)所有類加載器的父級,負(fù)責(zé)加載JDK內(nèi)部類。擴(kuò)展類加載器和系統(tǒng)類加載器分別從Java擴(kuò)展目錄和類路徑加載類。然后,我們學(xué)習(xí)了類加載器的工作原理、特性,以及如何創(chuàng)建自定義類加載器。


分享文章:這一篇文章,可以把Java中的類加載器了解的七七八八了
鏈接分享:http://www.5511xx.com/article/dpecise.html