Java類加載器的作用有哪些?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
Java的類加載器結構為下圖所示
關于三層類加載器、雙親委派機制,本文不再板書,讀者可自行百度。
那么在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
protected final Class<?> defineClass(String name, byte[] b, int off,int len)
protected final void resoiveClass(class<?> c)
protected final Class<?> findLoadedClass(String name)
private final ClassLoader parent;
關于這些方法,不一一展開,主要看一下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()返回的,與數組當中元素類型的類加載器是一樣的,如果數組當中的元素類型是基本數據類型,數組類是沒有類加載器的。
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。