上文說了類加載過程的5個階段,着重介紹了各個階段作的工做。在本文中,咱們對執行加載階段的主體進行探討,學習類加載器的模型和邏輯,以及咱們該如何自定義一個類加載器。java
前面說過加載階段是一個可讓設計人員高度自控的模塊,由於類文件的源頭能夠是多種多樣的,代碼生成、反射生成或從網絡中生成等。所以類加載器做爲對這些文件的處理就顯得尤其重要。數組
但類加載器的功能不只如此,其還有一個重要的功能就是和一個類的全限定名惟一肯定一個類。通俗來講,要說兩個類是相同的,不只其全限定名要同樣,其對應的類加載器也必須相同,才能說明兩個類是相等的。網絡
正由於類加載器的功能角色如此重要,所以虛擬機對其的實現規範也十分重視。在Java虛擬機中,對其的實現模型是雙親委派模型。jvm
雙親委派模型的主要執行過程示意圖如上所示,其分爲啓動類加載器(Bootstrap Class-loader),拓展類加載器(Extension Class-loader),應用程序類加載(Application Class-loader)。ide
其中啓動類加載器主要負責加載 JRE 的核心類庫,如 JRE 目錄下的 rt.jar。但其實根據《深刻分析 Java Web 技術內幕》上所說,啓動類加載器並不嚴格符合雙親委派模型,由於Bootstrap Class-loader 並不屬於 JVM 的類等級層次。Bootstrap Class-loader 是沒有子類的,Extension Class-loader 也是沒有父類的。不過在這裏咱們並不深究,只要知道有這一點就能夠了。源碼分析
Extension Class-loader 主要負責加載 JRE 拓展目錄 ext 下的類。佈局
Application Class-loader 主要負責用戶類路徑(Class-path)下的類,這個類加載器是使用的最多的,由於大大多數狀況下,通常開發者並無實現自定義的類加載器,那麼 JVM 就會使用這個來加載類大部分類。學習
上圖就是雙親委派模型的執行過程,當類開始加載的時候,先檢查是否已經被加載過,若是沒有被加載過,則調用父類的加載方法,若是父類加載失敗,拋出異常,則調用自身的 findClass() 方法進行加載。this
JDK 中加載過程的源碼分析:url
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 { // 無父親就調用 bootstarp 加載器來加載 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 父加載器和 bootstarp 加載器都沒有找到指定類,調用當前類的 findClass() 來完成類加載 // 所以,自定義類加載器,就是重寫 findClass() 方法 if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); 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() 來實現的。
/** * 1. 自定義類加載器經過集成ClassLoader來實現,主要經過重寫findClass方法 * 2. findClass方法首先經過自定義的loadByte()方法將Class文件轉換成byte[]字節流 * 3. 而後經過defineClass()方法將其轉換爲Class對象 */ public class SelfClassLoader extends ClassLoader { private String classPath; public SelfClassLoader(String classPath) { this.classPath = classPath; } /** * 經過 difineClass,將一個字節數組轉換爲Class 對象 * @param name * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } /** * 根據路徑將指定的文件讀取爲byte 流 * @param name * @return * @throws IOException */ private byte[] loadByte(String name) throws IOException { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } }
另外一個種實現自定義類加載器的方法:
/** * 1. 加載指定packageName下的類 * 2. 用自定義類加載器進行加載,若是加載失敗,再交給父加載器進行加載 */ public class UrlSelfClassloader extends URLClassLoader { private String packageName = ""; public UrlSelfClassloader(URL[] urls, ClassLoader parent) { super(urls, parent); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> aClass = findLoadedClass(name); if (Objects.nonNull(aClass)){ return aClass; } if (!packageName.startsWith(name)){ return super.loadClass(name); }else { return findClass(name); } } }
如何使用自定義的類加載器
public static void main(String args[]) throws Exception { MyClassLoader classLoader = new MyClassLoader(""); Class clazz = classLoader.loadClass(""); Object obj = clazz.newInstance(); Method helloMethod = clazz.getDeclaredMethod("hello", null); helloMethod.invoke(obj, null); }
在本文中,咱們講解了類加載器的實現模型,分析了在 JDK 中類加載器的源碼實現,並根據源碼中的代碼實現,自定義了一個類加載器的實現。
此外相信通過五和六兩篇文章的學習,你們應該對如何將類加載入虛擬機中有了系統的理解。
後面的文章中,咱們就要進入 JVM 的內部了,從下篇文章開始,咱們就開始逐步講解 JVM 的內存佈局,瞭解 JVM 中的各個邏輯上劃分的存儲結構以及其做用,歡迎各位讀者瀏覽。
文章在公衆號「iceWang」第一手更新,有興趣的朋友能夠關注公衆號,第一時間看到筆者分享的各項知識點,謝謝!筆芯!
本系列文章主要借鑑《深刻分析 Java Web 技術內幕》和《深刻理解 Java 虛擬機 - JVM 高級特性與最佳實踐》。