本篇內(nèi)容主要講解“什么是類加載器”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“什么是類加載器”吧!
成都創(chuàng)新互聯(lián)公司專注于雨花企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站建設(shè),商城建設(shè)。雨花網(wǎng)站建設(shè)公司,為雨花等地區(qū)提供建站服務(wù)。全流程按需網(wǎng)站設(shè)計,專業(yè)設(shè)計,全程項目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務(wù)
Java程序被編譯器編譯之后成為字節(jié)碼文件(.class文件),當程序需要某個類時,虛擬機便會將對應(yīng)的class文件進行加載,創(chuàng)建出對應(yīng)的Class對象。而這個將class文件加載到虛擬機內(nèi)存的過程,便是類加載。
類加載器負責在運行時將Java類動態(tài)加載到JVM(Java虛擬機),是JRE(Java運行時環(huán)境)的一部分。由于類加載器的存在,JVM無需了解底層文件或文件系統(tǒng)即可運行Java程序。
Java類不會一次全部加載到內(nèi)存中,而是在應(yīng)用程序需要時才會加載。此時,類加載器負責將類加載到內(nèi)存中。
類的生命周期通常包括:加載、鏈接、初始化、使用和卸載。上圖中包含了類加載的三個階段:加載階段、鏈接階段和初始化階段。如果將這三個階段再拆分細化包括:加載、驗證、準備、解析和初始化。
關(guān)于這幾個階段的作用,已經(jīng)有很多文章在寫了,我們就簡單概況一下:
加載:通過一個類的完全限定查找類字節(jié)碼文件,轉(zhuǎn)化為方法區(qū)運行時的數(shù)據(jù)結(jié)構(gòu),創(chuàng)建一個代表該類的Class對象。
驗證:確保Class文件的字節(jié)流中包含信息符合當前虛擬機要求,不會危害虛擬機自身安全。
準備:為類變量(即static修飾的字段變量)分配內(nèi)存并且設(shè)置該類變量的初始值。不包含被final修飾的static變量,因為它在編譯時已經(jīng)分配了。
解析:將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程。如果符號引用指向一個未被加載的類,或者未被加載類的字段或方法,那么解析將觸發(fā)這個類的加載。
初始化:類加載最后階段,若該類具有超類,則對其進行初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化成員變量。
在上述類加載的過程中,虛擬機內(nèi)部提供了三種類加載器:啟動(Bootstrap)類加載器、擴展(Extension)類加載器、系統(tǒng)(System)類加載器(也稱應(yīng)用類加載器)。
下面就討論不同類型的內(nèi)置類加載器是如何工作,以及介紹如何自定義類加載器。
先從一個簡單的例子來看一下如何使用不同的加載器來加載不同的類:
public void printClassLoaders() { System.out.println("Classloader of this class:" + PrintClassLoader.class.getClassLoader()); System.out.println("Classloader of Logging:" + Logging.class.getClassLoader()); System.out.println("Classloader of ArrayList:" + ArrayList.class.getClassLoader()); }
執(zhí)行上述程序,打印如下內(nèi)容:
Classloader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2 Classloader of Logging:sun.misc.Launcher$ExtClassLoader@2f0e140b Classloader of ArrayList:null
上述三行輸出分別對應(yīng)三種不同的類加載器:系統(tǒng)(System)類加載器、擴展(Extension)類加載器和啟動(Bootstrap)類加載器(顯示為null)。
系統(tǒng)程序類加載器加載包含示例方法的類,也就是將我們自己的文件加載到類路徑中。擴展類加載器加載Logging類,也就是加載作為標準核心Java類擴展的類。啟動類加載器加載ArrayList類,是所有其他類的父級。
對于ArrayList的類加載器,輸出為null。這是因為啟動類加載器是用本機代碼實現(xiàn)而不是Java,因此它不會顯示為Java類。啟動類加載器在操作在不同的JVM中會有所不同。
上述三種類加載器,外加自定義類加載器,它們直接的關(guān)系可用下圖表示:
現(xiàn)在來具體看一下這些類加載器。
Java類由java.lang.ClassLoader的實例加載。但是,類加載器本身就是類。那么,誰來加載java.lang.ClassLoader?對,就是啟動類加載器。
啟動類加載器主要負責加載JDK內(nèi)部類,通常是rt.jar和$JAVA_HOME/jre/lib目錄中的其他核心庫。此外,Bootstrap類加載器還充當所有其他ClassLoader實例的父類。
該啟動程序類加載器是Java虛擬機的一部分,用本機代碼編寫(比如,C++),不同的平臺的實現(xiàn)可能有所不同。
出于安全考慮,Bootstrap啟動類加載器只加載包名為java、javax、sun等開頭的類。
擴展類加載器是啟動類加載器的子類,Java語言編寫,由sun.misc.Launcher$ExtClassLoader實現(xiàn),父類加載器為啟動類加載器,負責加載標準核心Java類的擴展。
擴展類加載器從JDK擴展目錄(通常是$JAVA_HOME/lib/ext目錄)或java.ext.dirs系統(tǒng)屬性中指定的任何其他目錄進行自動加載。
系統(tǒng)類加載器負責將所有應(yīng)用程序級類加載到JVM中。它加載在類路徑環(huán)境變量,-classpath或-cp命令行選項中找到的文件。它是擴展類加載器的子類。
系統(tǒng)類加載器,也稱應(yīng)用程序加載器是指 Sun公司實現(xiàn)的sun.misc.Launcher$AppClassLoader,負責加載系統(tǒng)類路徑-classpath或-D java.class.path指定路徑下的類庫,也就是我們經(jīng)常用到的classpath路徑,開發(fā)者可以直接使用系統(tǒng)類加載器,一般情況下該類加載是程序中默認的類加載器,通過ClassLoader#getSystemClassLoader()方法可以獲取到該類加載器。
類加載器是Java運行時環(huán)境的一部分。當JVM請求一個類時,類加載器將嘗試定位該類,并使用完全限定名將類定義裝入運行時。
java.lang.ClassLoader.loadClass()方法負責將類定義加載到運行時,它嘗試通過全限定名來加載類。如果未加載到該類,則它將請求委派給父類加載器。依次向上重復(fù)該過程。
最終,如果父類加載器找不到指定類,則子類將調(diào)用java.net.URLClassLoader.findClass()方法在文件系統(tǒng)本身中查找類。
如果最后一個子類加載器也無法加載該類,則它將拋出java.lang.NoClassDefFoundError或java.lang.ClassNotFoundException。拋出ClassNotFoundException時的輸出示例:
java.lang.ClassNotFoundException: com.baeldung.classloader.SampleClassLoader at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348)
上述過程,通常我們稱作雙親委派機制。雙親委派機制要求除了頂層的啟動類加載器外,其余的類加載器都應(yīng)當有自己的父類加載器,請注意雙親委派機制中的父子關(guān)系并非通常所說的類繼承關(guān)系,而是采用組合關(guān)系來復(fù)用父類加載器的相關(guān)代碼。
除外,類加載器還具有三個重要功能:委派模型、類的唯一性和可見性。
類加載器遵循委派模型,在該模型中,根據(jù)請求查找類或資源,ClassLoader實例會將對類或資源的搜索委托給父類加載器。
假設(shè)我們有一個將應(yīng)用程序類加載到JVM中的請求。系統(tǒng)類加載器首先將該類的加載委托給其父擴展類加載器,而父擴展類加載器又將其委托給引導(dǎo)類加載器。
僅當啟動類加載器和擴展類加載器都未能成功加載類時,系統(tǒng)類加載器才會嘗試加載類本身。
作為委托模型的結(jié)果,很容易確保類的唯一性,因為始終嘗試向上委托。如果父類加載器無法找到該類,則只有當前實例自己會嘗試進行查找和加載。
此外,子類加載器對其父類加載器加載的類可見。例如,系統(tǒng)類加載器加載的類對擴展和Bootstrap類加載器加載的類具有可見性,反之亦然。
比如,通過系統(tǒng)類加載器加載類A,而通過擴展類加載器加載類B,則對系統(tǒng)類加載器加載的其他類而言,A和B類都是可見的。但對擴展類加載器加載的其他類而言,類B是唯一可見的類。
在大多數(shù)情況下,如果文件已經(jīng)在文件系統(tǒng)中,則內(nèi)置的類加載器就足夠了。但是,在需要從本地硬盤驅(qū)動器或網(wǎng)絡(luò)中加載類的情況下,可能需要使用自定義類加載器。下面介紹自定義類加載器的使用。
自定義類加載器不僅對在運行時加載類有幫助,還有一些特殊的場景:
幫助修改現(xiàn)有的字節(jié)碼,例如weaving agents;
動態(tài)創(chuàng)建適合用戶需求的類。例如在JDBC中,通過動態(tài)類加載完成不同驅(qū)動程序?qū)崿F(xiàn)之間的切換。
在為具有相同名稱和程序包的類加載不同的字節(jié)碼時,實現(xiàn)類版本控制機制。這可以通過URL類加載器(通過URL加載jar)或自定義類加載器來完成。
舉一個更具體的例子,比如,瀏覽器使用自定義類加載器從網(wǎng)站加載可執(zhí)行內(nèi)容。瀏覽器可以使用單獨的類加載器從不同的網(wǎng)頁加載applet。用于運行applet的applet查看器包含一個ClassLoader,該類加載器可訪問遠程 在上面的示例中,我們定義了一個自定義類加載器,該類加載器擴展了默認類加載器并從指定文件加載字節(jié)數(shù)組。如果沒有太復(fù)雜的需求,可以直接繼承URLClassLoader類,重寫loadClass方法,具體可參考AppClassLoader和ExtClassLoader。 下面來看看java.lang.ClassLoader類中的一些基本方法,以更清楚地了解其工作方式。 loadClass方法 此方法負責加載給定名稱參數(shù)的類。name參數(shù)為類的全限定名。 Java虛擬機調(diào)用loadClass()方法來解析類引用,并將resolve設(shè)置為true。但是,不一定總是要解析一個類。如果只需要確定該類是否存在,則將resolve參數(shù)設(shè)置為false。 此方法用作類加載器的入口。我們可以嘗試從java.lang.ClassLoader的源代碼中了解loadClass()方法的內(nèi)部工作: 該方法的默認實現(xiàn)按以下順序搜索類: 調(diào)用findLoadedClass(String)方法以查看是否已加載該類。 在父類加載器上調(diào)用loadClass(String)方法。 調(diào)用findClass(String)方法以查找類。 此方法負責將字節(jié)數(shù)組轉(zhuǎn)換為類的實例。如果數(shù)據(jù)不包含有效的類,則會拋出ClassFormatError。另外,由于此方法被標記為final,因此我們無法覆蓋此方法。 此方法查找以標準名稱作為參數(shù)的類。我們需要在遵循委派模型加載類的自定義類加載器實現(xiàn)中重寫此方法。 另外,如果父類加載器找不到請求的類,則loadClass()會調(diào)用此方法。如果沒有任何類加載器的父類找到該類,則默認實現(xiàn)會拋出ClassNotFoundException異常。 此方法返回父類加載器以進行委派。某些實現(xiàn)使用null來表示啟動類加載器。 此方法嘗試查找具有給定名稱的資源。它將首先委托給資源的父類加載器,如果父級為null,則搜索虛擬機內(nèi)置的類加載器的路徑。如果失敗,則該方法將調(diào)用findResource(String)來查找資源。 指定為輸入的資源名稱可以相對于類路徑,也可以是相對于絕對路徑。 它返回用于讀取資源的URL對象;如果找不到資源或調(diào)用者沒有足夠的特權(quán)來返回資源,則返回null。 需要注意的是,Java是從類路徑中加載資源。 最后,Java中的資源加載被認為是與位置無關(guān)的,因為只要設(shè)置了環(huán)境來查找資源,代碼在何處運行都無關(guān)緊要。 通常,上下文類加載器為J2SE中引入的類加載委托方案提供了一種替代方法。JVM中的類加載器遵循分層模型,因此每個類加載器都有一個單獨的父類,而啟動類加載器除外。但是,有時當JVM核心類需要動態(tài)加載應(yīng)用程序開發(fā)人員提供的類或資源時,可能會遇到問題。 例如,在JNDI中,核心功能由rt.jar中的引導(dǎo)程序類實現(xiàn)。但是這些JNDI類可能會加載由獨立供應(yīng)商實現(xiàn)的JNDI提供程序(部署在應(yīng)用程序類路徑中)。這種情況要求啟動類加載器(父類加載器)加載對應(yīng)程序加載器(子類加載器)可見的類。 線程上下文類加載器(context class loader)是從JDK 1.2開始引入的。Java.lang.Thread中的方法 getContextClassLoader()和setContextClassLoader(ClassLoader cl)用來獲取和設(shè)置線程的上下文類加載器。如果沒有通過setContextClassLoader(ClassLoader cl)方法進行設(shè)置的話,線程將繼承其父線程的上下文類加載器。Java應(yīng)用運行的初始線程的上下文類加載器是系統(tǒng)類加載器,在線程中運行的代碼可以通過此類加載器來加載類和資源。 線程上下文類加載器從根本解決了一般應(yīng)用不能違背雙親委派模式的問題,使得java類加載體系顯得更靈活。上面所提到的問題正是線程上下文類加載器的拿手好菜。如果不做任何的設(shè)置,Java應(yīng)用的線程上下文類加載器默認就是系統(tǒng)類加載器。因此,在SPI接口的代碼中使用線程上下文類加載器,就可以成功的加載到SPI實現(xiàn)的類。 到此,相信大家對“什么是類加載器”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學習!了解java.lang.ClassLoader
public Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } }
defineClass方法
protected final Class defineClass( String name, byte[] b, int off, int len) throws ClassFormatError
findClass方法
protected Class> findClass( String name) throws ClassNotFoundException
getParent方法
public final ClassLoader getParent()
getResource方法
public URL getResource(String name)
上下文類加載器
名稱欄目:什么是類加載器
本文網(wǎng)址:http://weahome.cn/article/psjsid.html