原始碼解析Java類載入器
參考內容:
- 深入理解Java虛擬機器(JVM高階特性與最佳實踐) ——周志明老師
- 尚矽谷深入理解JVM教學視訊——宋紅康老師
我們都知道Java的類載入器結構為下圖所示(JDK8及之前,JDK9進行了模組化):
關於三層類載入器、雙親委派機制,本文不再板書,讀者可自行百度。
那麼在JDK的原始碼中,三層結構的具體實現是怎麼樣的呢?
Bootstrap ClassLoader(引導類載入器)
引導類載入器是由C++實現的,並非Java程式碼實現,所以在Java程式碼中是無法獲取到該類載入器的。
一般大家都稱類載入器分為四種(引導類、擴充套件類、系統類以及使用者自定義的類載入器),但其實在JVM虛擬機器規範中的支援兩種型別的類載入器,分別為引導類載入器(Bootstrap ClassLoader)和自定義類載入器(User-Defined ClassLoader),所以擴充套件類和系統類也可以統稱為自定義類載入器。
Extension ClassLoader(擴充套件類載入器)和Appclass Loader(系統類載入器)
擴充套件類載入器和系統類載入器都是由Java語言編寫,具體實現為sum.misc.Launcher中的兩個內部類ExtClassLoader和AppClassLoader實現,我們進入到LaunchLacher這個類中看看(這個類在oracle jdk是沒有公開原始碼的,需要看具體原始碼的讀者可以下載open jdk中檢視具體原始碼,筆者這裡就只是使用IDEA反編譯後生成的程式碼進行解析):
首先是Laucncher的構造方法:
public Launcher() { Launcher.ExtClassLoader var1; try { // 獲取擴充套件類載入器 var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader",var10); } try { // 獲取系統類載入器 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader",var9); } // 此處是將系統類載入器設定為當前執行緒的上下文載入器 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的構造方法中定義了一個Launcher.ExtClassLoader型別的區域性變數var1(這裡是反編譯後的變數名),並呼叫Launcher.ExtClassLoader.getExtClassLoader()方法給該區域性變數賦值,以及呼叫Launcher.AppClassLoader.getAppClassLoader(var1);給例項變數(型別為Launcher.AppClassLoader)賦值,需要注意的是,在給系統類載入器賦值時,將擴充套件類載入器作為引數傳入到了方法中。
同時,在構造方法中,將系統類載入器設定為了當前執行緒的上下文類載入器,關於上下文類載入器,主要用於基礎型別呼叫回用戶程式碼時方法父類載入器區請求子類載入器完成類載入的行為,主要用於JDBC、JNDI等SPI服務提供者介面,這裡不詳細展開。
上述原始碼中的**getExtClassLoader()與getAppClassLoader()**方法原始碼如下:
getExtClassLoader()是Launcher中的內部類ExtClassLoader(擴充套件類載入器)的一個靜態方法:
// 這是ExtClassLoader類內部的定義 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(); // 建立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); // 呼叫構造方法 } }); } catch (PrivilegedActionException var1) { throw (IOException)var1.getException(); } } // ExtClassLoader的構造方法 public ExtClassLoader(File[] var1) throws IOException { // 此處第二個引數需要格外注意!!,我們進入父類的構造方法檢視該引數是什麼 super(getExtURLs(var1),(ClassLoader)null,Launcher.factory); SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this); } // 父類URLClassLoader的構造方法 // 此處的第二個引數是父類構造器的引用,也就解釋了為什麼在呼叫獲得ExtClassLoader的 public URLClassLoader(URL[] urls,ClassLoader parent,getParent()方法獲取父類構造器為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中的內部類AppClassLoader(系統類載入器)的一個靜態方法:
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); // 與擴充套件類載入器不同的是,系統類載入器並不是單例模式的 } }); } // AppClassLoader的構造方法 AppClassLoader(URL[] var1,ClassLoader var2) { // 這裡的var2 對應上述getAppClassLoader()方法中的var0,而var0對應的就是Launcher的構造方法中獲取到的ExtClassLoader // 在ExtClassLoader原始碼的分析中,我們知道這個var2代表的就是父類構造器,所以此處就是將AppClassLoader的父類設定為ExtClassLoader super(var1,var2,Launcher.factory); this.ucp.initLookupCache(this); }
通過上述兩個方法,就可以解釋為什麼在獲取擴充套件類載入器的父類時為null(即引導載入器),以及不同類載入器看似是繼承(Inheritance)關係,實際上是包含關係。在下層載入器中,包含著上層載入器的引用。
ClassLoader抽象類
上述的ExtClassLoader和AppClassLoader均繼承於ClassLoader類,ClassLoader抽象類也是類載入機制的基石,接下來我們就進入到該類中,看看它的一些主要方法。
public final classLoader getParent()
返回該類載入器的超類載入器
public Class<?>loadclass(String name) throws ClassNotFoundException
載入名稱為name的類,返回結果為java.lang.Class類的例項。如果找不到類,則返ClassNotFoundException異常。該方法中的邏輯就是雙親委派模式的實現。
protected class<?> findClass(string name)throws ClassNotFoundException
- 查詢二進位制名稱為name的類,返回結果為java.lang.Class類的例項。這是一個受保護的方法,JVM鼓勵我們重寫此方法,需要自定義載入器遵循雙親委託機制,該方法會在檢查完父類載入器之後被loadClass()方法呼叫。
- 在JDK1.2之前,在自定義類載入時,總會去繼承ClassLoader類並重寫loadClass方法,從而實現自定義的類載入類。但是在JDK1.2之後已不再建議使用者去覆蓋loadClass()方法,而是建議把自定義的類載入邏輯寫在findClass()方法中,從前面的分析可知, findClass()方法是在loadClass()方法中被呼叫的,當loadclass()方法中父載入器載入失敗後,則會呼叫自己的findClass()方法來完成類載入,這樣就可以保證自定義的類載入器也符合雙親委託模式。
- 需要注意的是ClassLoader類中並沒有實現findClass()方法的具體程式碼邏輯,取而代之的是丟擲ClassNotFoundException異常,同時應該知道的是findClass方法通常是和defineClass方法一起使用的。一般情況下,在自定義類載入器時,會直接覆蓋ClassLoader的findClass()方法並編寫載入規則,取得要載入類的位元組碼後轉換成流,然後呼叫defineClass()方法生成類的Class物件。
protected final Class<?> defineClass(String name,byte[] b,int off,int len)
- 根據給定的位元組陣列b轉換為Class的例項,off和len引數表示實際Class資訊在byte陣列中的位置和長度,其中byte陣列b是ClassLoader從外部獲取的。這是受保護的方法,只有在自定義ClassLoader子類中可以使用。
- defineClass()方法是用來將byte位元組流解析郕VM能夠識別的cClass物件(ClassLoader中已實現該方法邏輯),通過這個方法不僅能夠通過class檔案例項化class物件,也可以通過其他方式例項化class物件,如通過網路接收一個類的位元組碼,然後轉換為byte位元組流建立對應的Class物件。
- defineClass()方法通常與findClass()方法一起使用,一般情況下,在自定義類載入器時,會直接覆蓋ClassLoader的findClass()方法並編寫載入規則,取得要載入炎的位元組碼後轉換成流,然後呼叫defineClass()方法生成類的Class物件。
protected final void resoiveClass(class<?> c)
- 連結指定的一個Java類。使用該方法可以使用類的Class物件建立完成的同時也被解析。前面我們說連結階段主要是對位元組碼進行驗證,為類變數分配記憶體並設定初始值同時將位元組碼檔案中的符號引用轉換為直接引用。
protected final Class<?> findLoadedClass(String name)
- 查詢名稱為name的已經被載入過的類,返回結果為java.lang.Class類的例項。這個方法是final方法,無法被修改。
private final ClassLoader parent;
- 它也是一個ClassLoader的例項,這個欄位所表示的ClassLoader也稱為這個ClassLoader的雙親。在類載入的過程中,classLoader可能會將某些請求交予自己的雙親處理。
關於這些方法,不一一展開,主要看一下loadClass()和findClass()。
loadClass()
public Class<?> loadClass(String name) throws ClassNotFoundException { // loadClass呼叫過載含有兩個引數的loadClass,其中第二個引數表示在載入時是否解析,預設為false return loadClass(name,false); } // 含有兩個引數的過載loadClass方法 protected Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException{// resolve:true->載入class的同時進行解析操作 synchronized (getClassLoadingLock(name)) {// 同步操作,保證只能載入一次 // 首先在快取中判斷是否已經載入同名的類 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); // 此處就是雙親委派機制的具體實現,其實就是讓父類載入器先去載入。 try { // 獲取當前類載入器的父類載入器 if (parent != null) { // 如果存在父類載入器,則呼叫父類載入器的loadClass進行載入(雙親委派) c = parent.loadClass(name,false); } else { // parent == null:父類載入器是引導類載入器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 當前類載入器的父類載入器未載入此類 or 當前類載入器未載入此類 if (c == null) { // If still not found,then invoke findClass in order // to find the class. long t1 = System.nanoTime(); // 呼叫當前類載入器的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()方法直接丟擲異常,所以具體的實現是由子類進行重寫實現了;在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 { // 呼叫defineClass()方法獲得要載入的類對應的Class物件, // defineClass()的作用就是根據給定的class原始檔返回一個對應的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; }
最後補充一點關於陣列類載入的細節
陣列類的Class物件,不是由類載入器去建立的,而是在Java執行期JVM根據需要自動建立的。對於陣列類的類載入器來說,是通過Class.getClassLoader()返回的,與陣列當中元素型別的類載入器是一樣的,如果陣列當中的元素型別是基本資料型別,陣列類是沒有類載入器的。
到此這篇關於Java類載入器的文章就介紹到這了,更多相關Java類載入器內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!