源碼解析Java類加載器
參考內(nèi)容:
- 深入理解Java虛擬機(JVM高級特性與最佳實踐) ——周志明老師
- 尚硅谷深入理解JVM教學(xué)視頻——宋紅康老師
我們都知道Java的類加載器結(jié)構(gòu)為下圖所示(JDK8及之前,JDK9進行了模塊化):
關(guān)于三層類加載器、雙親委派機制,本文不再板書,讀者可自行百度。
那么在JDK的源碼中,三層結(jié)構(gòu)的具體實現(xiàn)是怎么樣的呢?
Bootstrap ClassLoader(引導(dǎo)類加載器)
引導(dǎo)類加載器是由C++實現(xiàn)的,并非Java代碼實現(xiàn),所以在Java代碼中是無法獲取到該類加載器的。
一般大家都稱類加載器分為四種(引導(dǎo)類、擴展類、系統(tǒng)類以及用戶自定義的類加載器),但其實在JVM虛擬機規(guī)范中的支持兩種類型的類加載器,分別為引導(dǎo)類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader),所以擴展類和系統(tǒng)類也可以統(tǒng)稱為自定義類加載器。
Extension ClassLoader(擴展類加載器)和Appclass Loader(系統(tǒng)類加載器)
擴展類加載器和系統(tǒng)類加載器都是由Java語言編寫,具體實現(xiàn)為sum.misc.Launcher中的兩個內(nèi)部類ExtClassLoader和AppClassLoader實現(xiàn),我們進入到LaunchLacher這個類中看看(這個類在oracle jdk是沒有公開源碼的,需要看具體源碼的讀者可以下載open jdk中查看具體源碼,筆者這里就只是使用IDEA反編譯后生成的代碼進行解析):
首先是Laucncher的構(gòu)造方法:
public Launcher() { Launcher.ExtClassLoader var1; try { // 獲取擴展類加載器 var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { // 獲取系統(tǒng)類加載器 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } // 此處是將系統(tǒng)類加載器設(shè)置為當(dāng)前線程的上下文加載器 Thread.currentThread().setContextClassLoader(this.loader); String var2 = System.getProperty("java.security.manager"); if (var2 != null) { SecurityManager var3 = null; if (!"".equals(var2) && !"default".equals(var2)) { try { var3 = (SecurityManager)this.loader.loadClass(var2).newInstance(); } catch (IllegalAccessException var5) { } catch (InstantiationException var6) { } catch (ClassNotFoundException var7) { } catch (ClassCastException var8) { } } else { var3 = new SecurityManager(); } if (var3 == null) { throw new InternalError("Could not create SecurityManager: " + var2); } System.setSecurityManager(var3); } }
可以看到在Launcher的構(gòu)造方法中定義了一個Launcher.ExtClassLoader類型的局部變量var1(這里是反編譯后的變量名),并調(diào)用Launcher.ExtClassLoader.getExtClassLoader()方法給該局部變量賦值,以及調(diào)用Launcher.AppClassLoader.getAppClassLoader(var1);給實例變量(類型為Launcher.AppClassLoader)賦值,需要注意的是,在給系統(tǒng)類加載器賦值時,將擴展類加載器作為參數(shù)傳入到了方法中。
同時,在構(gòu)造方法中,將系統(tǒng)類加載器設(shè)置為了當(dāng)前線程的上下文類加載器,關(guān)于上下文類加載器,主要用于基礎(chǔ)類型調(diào)用回用戶代碼時方法父類加載器區(qū)請求子類加載器完成類加載的行為,主要用于JDBC、JNDI等SPI服務(wù)提供者接口,這里不詳細展開。
上述源碼中的**getExtClassLoader()與getAppClassLoader()**方法源碼如下:
getExtClassLoader()是Launcher中的內(nèi)部類ExtClassLoader(擴展類加載器)的一個靜態(tài)方法:
// 這是ExtClassLoader類內(nèi)部的定義 private static volatile Launcher.ExtClassLoader instance;// 單例模式實例對象 public static Launcher.ExtClassLoader getExtClassLoader() throws IOException { // 從這里可以看出,ExtClassLoader是一個由double-checking形成的懶漢式單例對象 if (instance == null) { Class var0 = Launcher.ExtClassLoader.class; synchronized(Launcher.ExtClassLoader.class) { if (instance == null) { instance = createExtClassLoader(); // 創(chuàng)建ExtClassLoader } } } return instance; } // createExtClassLoader()方法 private static Launcher.ExtClassLoader createExtClassLoader() throws IOException { try { return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() { public Launcher.ExtClassLoader run() throws IOException { File[] var1 = Launcher.ExtClassLoader.getExtDirs(); int var2 = var1.length; for(int var3 = 0; var3 < var2; ++var3) { MetaIndex.registerDirectory(var1[var3]); } return new Launcher.ExtClassLoader(var1); // 調(diào)用構(gòu)造方法 } }); } catch (PrivilegedActionException var1) { throw (IOException)var1.getException(); } } // ExtClassLoader的構(gòu)造方法 public ExtClassLoader(File[] var1) throws IOException { // 此處第二個參數(shù)需要格外注意??!,我們進入父類的構(gòu)造方法查看該參數(shù)是什么 super(getExtURLs(var1), (ClassLoader)null, Launcher.factory); SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this); } // 父類URLClassLoader的構(gòu)造方法 // 此處的第二個參數(shù)是父類構(gòu)造器的引用,也就解釋了為什么在調(diào)用獲得ExtClassLoader的 public URLClassLoader(URL[] urls, ClassLoader parent, getParent()方法獲取父類構(gòu)造器為null URLStreamHandlerFactory factory) { super(parent); // this is to make the stack depth consistent with 1.1 SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkCreateClassLoader(); } acc = AccessController.getContext(); ucp = new URLClassPath(urls, factory, acc); }
getAppClassLoader()是Launcher中的內(nèi)部類AppClassLoader(系統(tǒng)類加載器)的一個靜態(tài)方法:
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { final String var1 = System.getProperty("java.class.path"); final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1); return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() { public Launcher.AppClassLoader run() { URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2); return new Launcher.AppClassLoader(var1x, var0); // 與擴展類加載器不同的是,系統(tǒng)類加載器并不是單例模式的 } }); } // AppClassLoader的構(gòu)造方法 AppClassLoader(URL[] var1, ClassLoader var2) { // 這里的var2 對應(yīng)上述getAppClassLoader()方法中的var0,而var0對應(yīng)的就是Launcher的構(gòu)造方法中獲取到的ExtClassLoader // 在ExtClassLoader源碼的分析中,我們知道這個var2代表的就是父類構(gòu)造器,所以此處就是將AppClassLoader的父類設(shè)置為ExtClassLoader super(var1, var2, Launcher.factory); this.ucp.initLookupCache(this); }
通過上述兩個方法,就可以解釋為什么在獲取擴展類加載器的父類時為null(即引導(dǎo)加載器),以及不同類加載器看似是繼承(Inheritance)關(guān)系,實際上是包含關(guān)系。在下層加載器中,包含著上層加載器的引用。
ClassLoader抽象類
上述的ExtClassLoader和AppClassLoader均繼承于ClassLoader類,ClassLoader抽象類也是類加載機制的基石,接下來我們就進入到該類中,看看它的一些主要方法。
public final classLoader getParent()
返回該類加載器的超類加載器
public Class<?>loadclass(String name) throws ClassNotFoundException
加載名稱為name的類,返回結(jié)果為java.lang.Class類的實例。如果找不到類,則返ClassNotFoundException異常。該方法中的邏輯就是雙親委派模式的實現(xiàn)。
protected class<?> findClass(string name)throws ClassNotFoundException
- 查找二進制名稱為name的類,返回結(jié)果為java.lang.Class類的實例。這是一個受保護的方法,JVM鼓勵我們重寫此方法,需要自定義加載器遵循雙親委托機制,該方法會在檢查完父類加載器之后被loadClass()方法調(diào)用。
- 在JDK1.2之前,在自定義類加載時,總會去繼承ClassLoader類并重寫loadClass方法,從而實現(xiàn)自定義的類加載類。但是在JDK1.2之后已不再建議用戶去覆蓋loadClass()方法,而是建議把自定義的類加載邏輯寫在findClass()方法中,從前面的分析可知, findClass()方法是在loadClass()方法中被調(diào)用的,當(dāng)loadclass()方法中父加載器加載失敗后,則會調(diào)用自己的findClass()方法來完成類加載,這樣就可以保證自定義的類加載器也符合雙親委托模式。
- 需要注意的是ClassLoader類中并沒有實現(xiàn)findClass()方法的具體代碼邏輯,取而代之的是拋出ClassNotFoundException異常,同時應(yīng)該知道的是findClass方法通常是和defineClass方法一起使用的。一般情況下,在自定義類加載器時,會直接覆蓋ClassLoader的findClass()方法并編寫加載規(guī)則,取得要加載類的字節(jié)碼后轉(zhuǎn)換成流,然后調(diào)用defineClass()方法生成類的Class對象。
protected final Class<?> defineClass(String name, byte[] b, int off,int len)
- 根據(jù)給定的字節(jié)數(shù)組b轉(zhuǎn)換為Class的實例,off和len參數(shù)表示實際Class信息在byte數(shù)組中的位置和長度,其中byte數(shù)組b是ClassLoader從外部獲取的。這是受保護的方法,只有在自定義ClassLoader子類中可以使用。
- defineClass()方法是用來將byte字節(jié)流解析郕VM能夠識別的cClass對象(ClassLoader中已實現(xiàn)該方法邏輯),通過這個方法不僅能夠通過class文件實例化class對象,也可以通過其他方式實例化class對象,如通過網(wǎng)絡(luò)接收一個類的字節(jié)碼,然后轉(zhuǎn)換為byte字節(jié)流創(chuàng)建對應(yīng)的Class對象。
- defineClass()方法通常與findClass()方法一起使用,一般情況下,在自定義類加載器時,會直接覆蓋ClassLoader的findClass()方法并編寫加載規(guī)則,取得要加載炎的字節(jié)碼后轉(zhuǎn)換成流,然后調(diào)用defineClass()方法生成類的Class對象。
protected final void resoiveClass(class<?> c)
- 鏈接指定的一個Java類。使用該方法可以使用類的Class對象創(chuàng)建完成的同時也被解析。前面我們說鏈接階段主要是對字節(jié)碼進行驗證,為類變量分配內(nèi)存并設(shè)置初始值同時將字節(jié)碼文件中的符號引用轉(zhuǎn)換為直接引用。
protected final Class<?> findLoadedClass(String name)
- 查找名稱為name的已經(jīng)被加載過的類,返回結(jié)果為java.lang.Class類的實例。這個方法是final方法,無法被修改。
private final ClassLoader parent;
- 它也是一個ClassLoader的實例,這個字段所表示的ClassLoader也稱為這個ClassLoader的雙親。在類加載的過程中,classLoader可能會將某些請求交予自己的雙親處理。
關(guān)于這些方法,不一一展開,主要看一下loadClass()和findClass()。
loadClass()
public Class<?> loadClass(String name) throws ClassNotFoundException { // loadClass調(diào)用重載含有兩個參數(shù)的loadClass,其中第二個參數(shù)表示在加載時是否解析,默認為false return loadClass(name, false); } // 含有兩個參數(shù)的重載loadClass方法 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{// resolve:true->加載class的同時進行解析操作 synchronized (getClassLoadingLock(name)) {// 同步操作,保證只能加載一次 // 首先在緩存中判斷是否已經(jīng)加載同名的類 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); // 此處就是雙親委派機制的具體實現(xiàn),其實就是讓父類加載器先去加載。 try { // 獲取當(dāng)前類加載器的父類加載器 if (parent != null) { // 如果存在父類加載器,則調(diào)用父類加載器的loadClass進行加載(雙親委派) c = parent.loadClass(name, false); } else { // parent == null:父類加載器是引導(dǎo)類加載器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 當(dāng)前類加載器的父類加載器未加載此類 or 當(dāng)前類加載器未加載此類 if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); // 調(diào)用當(dāng)前類加載器的findClass() c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) {// 是否進行解析操作 resolveClass(c); } return c; } }
findClass()
//在ClassLoader中的findClass()方法 rotected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
可以看到,在ClassLoader中的findCLass()方法直接拋出異常,所以具體的實現(xiàn)是由子類進行重寫實現(xiàn)了;在ClassLoader的子類SecureClassLoader的子類URLClassLoader中對該方法進行了重寫。
URLClassLoader中的findCLass()方法
protected Class<?> findClass(final String name) throws ClassNotFoundException { final Class<?> result; try { result = AccessController.doPrivileged( new PrivilegedExceptionAction<Class<?>>() { public Class<?> run() throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class");// 類名路徑字符串格式替換 Resource res = ucp.getResource(path, false);// 獲得class源文件 if (res != null) { try { // 調(diào)用defineClass()方法獲得要加載的類對應(yīng)的Class對象, // defineClass()的作用就是根據(jù)給定的class源文件返回一個對應(yīng)的Class對象 return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { return null; } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } if (result == null) { throw new ClassNotFoundException(name); } return result; }
最后補充一點關(guān)于數(shù)組類加載的細節(jié)
數(shù)組類的Class對象,不是由類加載器去創(chuàng)建的,而是在Java運行期JVM根據(jù)需要自動創(chuàng)建的。對于數(shù)組類的類加載器來說,是通過Class.getClassLoader()返回的,與數(shù)組當(dāng)中元素類型的類加載器是一樣的,如果數(shù)組當(dāng)中的元素類型是基本數(shù)據(jù)類型,數(shù)組類是沒有類加載器的。
到此這篇關(guān)于Java類加載器的文章就介紹到這了,更多相關(guān)Java類加載器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中動態(tài)規(guī)則的實現(xiàn)方式示例詳解
這篇文章主要介紹了Java中動態(tài)規(guī)則的實現(xiàn)方式,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08IDEA中request.getParameter爆紅問題及解決
這篇文章主要介紹了IDEA中request.getParameter爆紅問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11手把手教你在eclipse創(chuàng)建第一個java?web項目并運行
Eclipse是用來做開發(fā)的自由集成開發(fā)環(huán)境,這也是很多java程序員會使用的開發(fā)環(huán)境,所以可以使用eclipse創(chuàng)建javaweb項目,下面這篇文章主要給大家介紹了關(guān)于如何在eclipse創(chuàng)建第一個java?web項目并運行的相關(guān)資料,需要的朋友可以參考下2023-02-02SpringBoot集成pf4j實現(xiàn)插件開發(fā)功能的代碼示例
pf4j是一個插件框架,用于實現(xiàn)插件的動態(tài)加載,支持的插件格式(zip、jar),本文給大家介紹了SpringBoot集成pf4j實現(xiàn)插件開發(fā)功能的示例,文中通過代碼示例給大家講解的非常詳細,需要的朋友可以參考下2024-07-07SpringCloud?openfeign聲明式服務(wù)調(diào)用實現(xiàn)方法介紹
在springcloud中,openfeign是取代了feign作為負載均衡組件的,feign最早是netflix提供的,他是一個輕量級的支持RESTful的http服務(wù)調(diào)用框架,內(nèi)置了ribbon,而ribbon可以提供負載均衡機制,因此feign可以作為一個負載均衡的遠程服務(wù)調(diào)用框架使用2022-12-12