加載-雙親委派機制類加載的過程是較爲複雜的,今天來梳理下面試
如上圖,若是一個類收到類加載請求,它並不會本身先去加載類,而是把這個請求委託給父類加載器執行,若是父類加載器還有父類加載器,則會進一步向上委託,依次遞歸,直到請求到達啓動類加載器,若是父類加載器可以完成加載任務,則成功返回,若是父類加載器沒法完成加載任務,子類加載器會本身嘗試去加載,這就是雙親委派機制數據庫
public class ClassLoaderTest { public static void main(String[] args) { // 獲取系統類加載器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); // 獲取其上層:擴展類加載器 ClassLoader extClassLoader = systemClassLoader.getParent(); System.out.println(extClassLoader); // 獲取其上層:獲取不到啓動類加載器 ClassLoader bootstrapClassLoader = extClassLoader.getParent(); System.out.println(bootstrapClassLoader); // 對於用戶自定義類來講:默認使用系統類加載器進行加載 ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(classLoader); // String類使用啓動類加載器進行加載的 ClassLoader classLoader1 = String.class.getClassLoader(); System.out.println(classLoader1); // DNSNameService使用擴展類加載器加載 ClassLoader classLoader2 = DNSNameService.class.getClassLoader(); System.out.println(classLoader2); } }
以下,jdk的內置類加載器會默認加載一些jar包bootstrap
public class MyClassLoader extends ClassLoader { private String classLoaderName; private String path; public void setPath(String path) { this.path = path; } public MyClassLoader(String classLoaderName, String path) { // 將系統類加載器看成該類的父加載器 super(); this.classLoaderName = classLoaderName; this.path = path; } public byte[] loadClassData(String name) { System.out.println("abc"); InputStream is = null; byte[] data = null; ByteArrayOutputStream byteArrayOutputStream = null; try { this.classLoaderName = name.replace(".", "/"); String fileExtension = ".class"; is = new FileInputStream(path.concat(classLoaderName).concat(fileExtension)); byteArrayOutputStream = new ByteArrayOutputStream(); int ch = 0; while (-1 != (ch = is.read())) { byteArrayOutputStream.write(ch); } data = byteArrayOutputStream.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { assert is != null; is.close(); assert byteArrayOutputStream != null; byteArrayOutputStream.close(); } catch (Exception e) { e.printStackTrace(); } } return data; } @Override protected Class<?> findClass(String name) { byte[] data = loadClassData(name); return this.defineClass(name, data, 0, data.length); } }
測試一下,我刪除了target目錄下的MyTest.class,而後將MyTest.class移動到/Users/zhangxiaobin/Desktop這個目錄下,findClass時系統類加載器沒有加載到MyTest.class,自定義類加載器就可以加載到這個類了設計模式
public static void main(String[] args) throws Exception { // 定義第一個類加載器 MyClassLoader myClassLoader = new MyClassLoader("myClassLoader", "/Users/zhangxiaobin/Desktop/"); Class<?> clazz = myClassLoader.loadClass("com.example.jvm.MyTest"); Object object = clazz.newInstance(); System.out.println(object); System.out.println(clazz.getClassLoader()); System.out.println(clazz.getClassLoader().getParent()); System.out.println(clazz.hashCode()); // 定義第二個類加載器 MyClassLoader myClassLoader2 = new MyClassLoader("myClassLoader", "/Users/zhangxiaobin/Desktop/"); Class<?> clazz2 = myClassLoader2.loadClass("com.example.jvm.MyTest"); Object object2 = clazz2.newInstance(); System.out.println(object2); System.out.println(clazz.getClassLoader()); System.out.println(clazz.getClassLoader().getParent()); System.out.println(clazz2.hashCode()); }
測試一下,發現自定義了兩個類加載器,同一個類被加載了兩次,這是由於類加載器有一個命名空間的問題,每一個類加載器都有本身的命名空間,在同一個命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類,在不一樣的命名空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類多線程
將target目錄下的MyTest.class弄回來,會發現該類是使用了系統類加載器來加載的架構
在類加載器命名空間的限制下,雙親委派機制在某些場景下沒法知足咱們的需求,好比SPI機制併發
Java核心類庫定義了接口,並未給出實現,這些接口的實現來自不一樣的jar包(廠商),好比JDBC,Java核心類庫定義了Connection等接口,不一樣的廠商有不一樣的實現,MySQL、Oracle等等,這些實現是經過jar包的方式加載的,jar包位於ClassPath下。Java核心類庫是由啓動類加載器加載的,ClassPath下的jar包是由系統類加載器加載的,按照命名空間的規則,他們是不可見的app
辦法總比問題多,jdk在雙親委派機制的基礎上,新增了線程上下文類加載器,經過給當前線程設置線程上下文類加載器的方式來實現對於接口實現類的加載jvm
這個線程上下文類加載器通常是系統類加載器分佈式
// 具體設置的代碼在Launcher這個類中 public Launcher() { // Create the extension class loader ClassLoader extcl; try { // 獲取擴展類加載器 extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader", e); } // Now create the class loader to use to launch the application try { // 獲取系統類加載器 loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader", e); } // 將線程上下文類加載器設置爲系統類加載器,此加載器是能夠替換的 Thread.currentThread().setContextClassLoader(loader); .... }
SPI機制是經過ServiceLoader這個類進行實現類的加載
public static <S> ServiceLoader<S> load(Class<S> service) { // 能夠看到,獲取了線程上下文類加載器來加載類 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
示意圖以下
連接連接分爲三步,分別是驗證、準備、解析
驗證:確保Class文件中包含的信息符合Java虛擬機的規範
準備:設置類變量的默認初始值,注意:是類變量,而且被final修飾的變量會顯式初始化
解析:將符號引用轉化爲直接引用
初始化此階段是執行類的初始化器,進行類變量的初始化
public class MyTest2 { public static int a = 10; public static void main(String[] args) { System.out.println(MyTest2.a); } }
以下圖,使用jclasslib Bytecode Viewer能夠看到的信息,若是整個類沒有類變量,是不會出現,若是該類有父類,會執行父類的
最後一直想整理出一份完美的面試寶典,可是時間上一直騰不開,這套一千多道面試題寶典,結合今年金三銀四各類大廠面試題,以及 GitHub 上 star 數超 30K+ 的文檔整理出來的,我上傳之後,毫無心外的短短半個小時點贊量就達到了 13k,說實話仍是有點難以想象的。
內容涵蓋:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux等技術棧(485頁)
內容涵蓋:Java基礎、JVM、高併發、多線程、分佈式、設計模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、數據庫、雲計算等
因爲篇幅限制,詳解資料太全面,細節內容太多,因此只把部分知識點截圖出來粗略的介紹,每一個小節點裏面都有更細化的內容!
須要的小夥伴,能夠一鍵三連,點擊這裏獲取免費領取方式!