在 JVM 綜述裏面,咱們說,JVM 作了三件事情,Java 程序的內存管理,
Java Class 二進制字節流的加載(ClassLoader),Java 程序的執行(執行引擎)。咱們也說,咱們大部分狀況下只關注前2個。在前面的文章中,咱們已經分析了內存關係相關的,包括運行時數據區,GC 相關。今天咱們要講的就是類加載器。java
在 JVM 綜述 裏,咱們已經大體分析了一些概念。而今天的文章將詳細的闡述類加載器。程序員
首先,咱們要了解類加載器,固然,瞭解的目的是爲了更好的開發,經過對類加載器的解讀,看看咱們能不能作些什麼,好比修改類加載器的加載邏輯,好比加入自定義的類加載器等等功能。框架
讓咱們開始吧!ide
對於 Java 虛擬機來講,Class 文件是一個重要的接口,不管使用何種語言進行軟件開發,只要能將源文件編譯爲正確的 Class 文件,那麼這種語言就能夠在 Java 虛擬機上運行。能夠說,Class 文件就是虛擬機的基石。this
如圖所示:spa
從上圖能夠看出,虛擬機不拘泥於 Java 語言,任何一個源文件只要能編譯成 Class 文件的格式,就能夠在JVM 上運行!Class 文件格式就像是一個接口,只要遵照這個接口,就可以在 JVM 上運行。線程
Class 文件一般是以文件的方式存在(任何二進制流均可以是 Class 類型),但只有能被 JVM 加載後才能被使用,才能運行編譯後的代碼。系統裝在 Class 類型能夠分爲加載,連接和初始化三個步驟。其中,連接也可分爲驗證,準備和解析3步驟。如圖所示:設計
其中,只有加載過程是程序員可以控制的,後面的幾個步驟都是有虛擬機自動運行的。所以,咱們的關注點主要放在加載階段。code
上面說了,類加載器3個流程中,惟一能讓程序員 「作手腳」 的就是加載過程,上面是加載過程呢?其主要做用就是從系統外部得到 Class 二進制數據流。orm
JVM 不會無端裝載 Class 文件,只有在必要的時候才裝載,哪幾個時候呢?
以上6種狀況屬於主動調用,主動調用會觸發初始化,還有一種狀況是被動調用,則不會引發初始化。
Java 類加載器的具體實現就在 java.lang.ClassLoader,該類是一個抽象類,而且提供了一些重要的接口,用於自定義Class 的加載流程和加載方式。主要方法以下:
public Class<?> loadClass(String name) throws ClassNotFoundException
給定一個類名,加載一個雷,返回表明這個類的 Class 實例,若是找不到類,則返回異常。
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError
根據給定的字節碼流 b 定義一個類,off 表示位置,len 表示長度。該方法只有子類可使用。
protected Class<?> findClass(String name) throws ClassNotFoundException
查找一個類,也是隻能子類使用,這是重載 ClassLoader 時,最重要的系統擴展點。這個方法會被 loadClass 調用,用於自定義查找類的邏輯,若是不須要修改類加載默認機制,只是想改變類加載的形式,就能夠重載該方法。
protected final Class<?> findLoadedClass(String name)
一樣的,這個方法也只有子類可以使用,他會去尋找已經加載的類,這個方法是 final 方法,沒法被修改。
同時,在該類中,還有一個字段很是重要:parent,他也是一個 ClassLoader 的實例,這個字段所表示的 ClassLoader 也稱爲這個 ClassLoader 的雙親,在類加載的過程當中,ClassLoader 可能會將某些請求交給本身的雙親處理。
在標準的 Java 程序中,從虛擬機的角度講,只有2種類加載器:
從程序員的角度講,虛擬機會建立 3 中類加載器,分別是:Bootstrap ClassLoader(啓動類加載器),Extension ClassLoader(擴展類加載器)和 APPClassLoader(應用類加載器,也稱爲系統類加載器)。此外,每個應用程序還能夠擁有自定義的 ClassLoader,擴展 Java 虛擬機獲取 Class 數據的能力。
而這 3 個類加載器有着層次關係。
先來看一個著名的圖:
如圖所示:從 ClassLoader 的層次自頂向下爲啓動類加載器,擴展類加載器,應用類加載器和自定義類加載器,當系統須要適用一個類時,在判斷類是否已經被加載時,會先從當前底層類加載器進行判斷,但系統須要加載一個類時,會從頂層類開始加載,依次向下嘗試,直到成功。
注意,咱們沒法訪問啓動類加載器,當試圖獲取啓動類加載器的時候,返回 null,所以,若是返回的是 null,並不意味沒有類加載器爲它服務,而是指哪一個類爲啓動類加載器。
那麼這些類加載路徑是哪些呢?
BootStrap 類加載器負責加載
擴展類加載器有 sun.misc.Launcher$ExtClassLoader 實現,負責加載
應用類加載器由 sun.misc.Launcher$AppClassLoader 實現,因爲這個類是 ClassLoader 中的 getSystemClassLoader 方法的返回值,也稱爲系統類加載器,負載加載用戶類路徑(ClassPath)上所指定的類庫,開發者能夠直接使用這個類加載器。通常狀況下,這個就是程序中默認的類加載器。
自定義類加載器用於加載一些特殊途徑的類,通常也是用戶程序類。
系統中的 ClassLoader 在協同工做時,默認會使用雙親委託模式,即在類加載的時候,系統會判斷當前類是否已經被加載,若是已經加載,則直接返回,不然就嘗試加載,在嘗試加載時,會先請求雙親處理,若是雙親查找事變,則本身加載。代碼以下:
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 { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } 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; } }
代碼中,若是雙親是 null,則使用啓動類加載器加載,若是事變,則使用當前類加載器加載。
雙親爲 null 通常有2種狀況,1. 雙親是啓動類加載器。2. 本身就是啓動類加載器。
其中加載類的邏輯有2個注意的地方。
判斷是否已經加載?當判斷類是否須要加載時,是從底層開始判斷,若是底層已經加載了,則再也不請求雙親。
當系統準備加載一個類時。會先從雙親加載,也就是最頂層的啓動類加載器,逐層向下,直到找到該類。和上面的是相反的。
雙親模型當然有着優勢,可以讓整個系統保持了類的惟一性。但在有些場合,卻不適合,也就是說,頂層的啓動類加載器的代碼沒法訪問到底層的類加載器。如 rt.jar 沒法中代碼沒法訪問到應用類加載器。
你確定要問,爲何須要訪問呢?
在 Java 平臺中,把核心類(rt.jar)中提供外部服務,可由應用層自行實現的接口,一般能夠稱爲 Service Provider Interface,即 SPI。
在 rt.jar 中的抽象類須要加載繼承他們的在應用層的子類實現,可是以目前的雙親機制是沒法實現的。
所以 JDK 引用了一個不太優雅的設計,上下文類加載器。也就是講類加載放在線程上下文變量中。經過 Thread.getContextClassLoader(), Thread.setContextClassLoader(ClassLoader) 這兩個方法獲取和設置 ClassLoader,這樣,rt.jar 中的代碼就能夠獲取到底層的類加載了。
雙親模式是虛擬機的默認行爲,但並不是必須這麼作,經過重載 ClassLoader 能夠修改該行爲。事實上,不少框架和軟件都修改了,好比 Tomcat,OSGI。具體實現則是經過重寫 loadClass 方法,改變類的加載次序。好比先使用自定義類加載器加載,若是加載不到,則交給雙親加載。
咱們知道:由不一樣的 ClassLoader 加載的同名類屬於不一樣的類型,不能相互轉化和兼容。
而這個特性就是咱們實現熱替換的關鍵。過程如圖所示:
好了,到這裏,基本的類加載器就介紹結束了。咱們總結了類加載的工做流程,包括加載,鏈接,初始化。而後咱們重點介紹了加載,由於加載階段是咱們程序員惟一有所做爲的地方。而後介紹了加載階段的一些細節,好比雙親委派,而後說了雙親委派的缺點和補充,而後探討了如何修改默認的類加載方式,最後經過類加載的特性實現了熱替換。固然也看了核心類 ClassLoader 的源碼。不過,這確定不是類加載器的所有。咱們將在後面的文章中將類加載的其餘特性一一解開。
good luck!!!!