溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

什么是類加載器

發(fā)布時間:2021-10-13 09:09:53 來源:億速云 閱讀:190 作者:iii 欄目:編程語言

本篇內(nèi)容主要講解“什么是類加載器”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“什么是類加載器”吧!

什么是類加載器

類加載器簡介

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

什么是類加載器

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

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

類加載的過程

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

什么是類加載器

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

  • 加載:通過一個類的完全限定查找類字節(jié)碼文件,轉(zhuǎn)化為方法區(qū)運行時的數(shù)據(jù)結(jié)構(gòu),創(chuàng)建一個代表該類的Class對象。

  • 驗證:確保Class文件的字節(jié)流中包含信息符合當(dāng)前虛擬機(jī)要求,不會危害虛擬機(jī)自身安全。

  • 準(zhǔn)備:為類變量(即static修飾的字段變量)分配內(nèi)存并且設(shè)置該類變量的初始值。不包含被final修飾的static變量,因為它在編譯時已經(jīng)分配了。

  • 解析:將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程。如果符號引用指向一個未被加載的類,或者未被加載類的字段或方法,那么解析將觸發(fā)這個類的加載。

  • 初始化:類加載最后階段,若該類具有超類,則對其進(jìn)行初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化成員變量。

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

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

內(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)類加載器、擴(kuò)展(Extension)類加載器和啟動(Bootstrap)類加載器(顯示為null)。

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

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

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

什么是類加載器

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

Bootstrap類加載器

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

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

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

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

Extension類加載器

擴(kuò)展類加載器是啟動類加載器的子類,Java語言編寫,由sun.misc.Launcher$ExtClassLoader實現(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命令行選項中找到的文件。它是擴(kuò)展類加載器的子類。

系統(tǒng)類加載器,也稱應(yīng)用程序加載器是指  Sun公司實現(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運行時環(huán)境的一部分。當(dāng)JVM請求一個類時,類加載器將嘗試定位該類,并使用完全限定名將類定義裝入運行時。

什么是類加載器

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

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

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

委派模型

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

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

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

類的唯一性

作為委托模型的結(jié)果,很容易確保類的唯一性,因為始終嘗試向上委托。如果父類加載器無法找到該類,則只有當(dāng)前實例自己會嘗試進(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ò)中加載類的情況下,可能需要使用自定義類加載器。下面介紹自定義類加載器的使用。

自定義類加載器示例

自定義類加載器不僅對在運行時加載類有幫助,還有一些特殊的場景:

  • 幫助修改現(xiàn)有的字節(jié)碼,例如weaving agents;

  • 動態(tài)創(chuàng)建適合用戶需求的類。例如在JDBC中,通過動態(tài)類加載完成不同驅(qū)動程序?qū)崿F(xiàn)之間的切換。

  • 在為具有相同名稱和程序包的類加載不同的字節(jié)碼時,實現(xiàn)類版本控制機(jī)制。這可以通過URL類加載器(通過URL加載jar)或自定義類加載器來完成。

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

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

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

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

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

public class CustomClassLoader extends ClassLoader {      @Override     public Class findClass(String name) throws ClassNotFoundException {         byte[] b = loadClassFromFile(name);         return defineClass(name, b, 0, b.length);     }      private byte[] loadClassFromFile(String fileName)  {         InputStream inputStream = getClass().getClassLoader().getResourceAsStream(                 fileName.replace('.', File.separatorChar) + ".class");         byte[] buffer;         ByteArrayOutputStream byteStream = new ByteArrayOutputStream();         int nextValue = 0;         try {             while ( (nextValue = inputStream.read()) != -1 ) {                 byteStream.write(nextValue);             }         } catch (IOException e) {             e.printStackTrace();         }         buffer = byteStream.toByteArray();         return buffer;     } }

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

了解java.lang.ClassLoader

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

loadClass方法

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

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

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

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

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

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

  • 調(diào)用findLoadedClass(String)方法以查看是否已加載該類。

  • 在父類加載器上調(diào)用loadClass(String)方法。

  • 調(diào)用findClass(String)方法以查找類。

defineClass方法

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

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

findClass方法

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

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

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

getParent方法

public final ClassLoader getParent()

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

getResource方法

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)的,因為只要設(shè)置了環(huán)境來查找資源,代碼在何處運行都無關(guān)緊要。

上下文類加載器

通常,上下文類加載器為J2SE中引入的類加載委托方案提供了一種替代方法。JVM中的類加載器遵循分層模型,因此每個類加載器都有一個單獨的父類,而啟動類加載器除外。但是,有時當(dāng)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)方法進(jìn)行設(shè)置的話,線程將繼承其父線程的上下文類加載器。Java應(yīng)用運行的初始線程的上下文類加載器是系統(tǒng)類加載器,在線程中運行的代碼可以通過此類加載器來加載類和資源。

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

到此,相信大家對“什么是類加載器”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI