類的載入階段
類載入階段分為載入、連線、初始化三個階段,而載入階段需要通過類的全限定名來獲取定義了此類的二進位制位元組流。Java特意把這一步抽出來用類載入器來實現。把這一步驟抽離出來使得應用程式可以按需自定義類載入器。並且得益於類載入器,OSGI、熱部署等領域才得以在JAVA中得到應用。
在Java中任意一個類都是由這個類本身和載入這個類的類載入器來確定這個類在JVM中的唯一性。也就是你用你A類載入器載入的com.aa.ClassA
和你A類載入器載入的com.aa.ClassA
它們是不同的,也就是用instanceof
這種對比都是不同的。所以即使都來自於同一個class檔案但是由不同類載入器載入的那就是兩個獨立的類。
類載入器除了能用來載入類,還能用來作為類的層次劃分。Java自身提供了3種類載入器
1、啟動類載入器(Bootstrap ClassLoader),它是屬於虛擬機器自身的一部分,用C++實現的,主要負責載入<JAVA_HOME>\lib
目錄中或被-Xbootclasspath指定的路徑中的並且檔名是被虛擬機器識別的檔案。它等於是所有類載入器的爸爸。
2、擴充套件類載入器(Extension ClassLoader),它是Java實現的,獨立於虛擬機器,主要負責載入<JAVA_HOME>\lib\ext
目錄中或被java.ext.dirs系統變數所指定的路徑的類庫。
3、應用程式類載入器(Application ClassLoader),它是Java實現的,獨立於虛擬機器。主要負責載入使用者類路徑(classPath)上的類庫,如果我們沒有實現自定義的類載入器那這玩意就是我們程式中的預設載入器。
雙親委派模型
知道上面這幾個概念就能來看看雙親委派模型了。
雙親委派的意思是如果一個類載入器需要載入類,那麼首先它會把這個類請求委派給父類載入器去完成,每一層都是如此。一直遞迴到頂層,當父載入器無法完成這個請求時,子類才會嘗試去載入。這裡的雙親其實就指的是父類,沒有mother。父類也不是我們平日所說的那種繼承關係,只是呼叫邏輯是這樣。
{
// First, check if the class has already been loaded 先判斷class是否已經被載入過了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false); //找他爸爸去載入
} else {
c = findBootstrapClassOrNull(name); //沒爸爸說明是頂層了就用Bootstrap ClassLoader去載入
}
} 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;
}
複製程式碼
雙親委派模型不是一種強制性約束,也就是你不這麼做也不會報錯怎樣的,它是一種JAVA設計者推薦使用類載入器的方式。
雙親委派有啥好處呢?它使得類有了層次的劃分。就拿java.lang.Object
來說,你載入它經過一層層委託最終是由Bootstrap ClassLoader
來載入的,也就是最終都是由Bootstrap ClassLoader
去找<JAVA_HOME>\lib
中rt.jar裡面的java.lang.Object
載入到JVM中。
這樣如果有不法分子自己造了個java.lang.Object
,裡面嵌了不好的程式碼,如果我們是按照雙親委派模型來實現的話,最終載入到JVM中的只會是我們rt.jar裡面的東西,也就是這些核心的基礎類程式碼得到了保護。因為這個機制使得系統中只會出現一個java.lang.Object
。不會亂套了。你想想如果我們JVM裡面有兩個Object,那豈不是天下大亂了。
因此既然推薦使用這種模型當然是有道理了。
但是人生不如意事十之八九,有些情況不得不違反這個約束,例如JDBC。
你先得知道SPI(Service Provider Interface),這玩意和API不一樣,它是面向擴充的,也就是我定義了這個SPI,具體如何實現由擴充套件者實現。我就是定了個規矩。
JDBC就是如此,在rt.jar裡面定義了這個SPI,那mysql有mysql的jdbc實現,oracle有oracle的jdbc實現,反正我java不管你內部如何實現的,反正你們都得統一按我這個來,這樣我們java開發者才能容易的呼叫資料庫操作。所以因為這樣那就不得不違反這個約束啊,Bootstrap ClassLoader
就得委託子類來載入資料庫廠商們提供的具體實現。因為它的手只能摸到<JAVA_HOME>\lib
中,其他的它無能為力。這就違反了自下而上的委託機制了。
Java就搞了個執行緒上下文類載入器,通過setContextClassLoader()
預設情況就是應用程式類載入器然後Thread.current.currentThread().getContextClassLoader()
獲得類載入器來載入。
如有錯誤歡迎指正!
個人公眾號:yes的練級攻略
有相關面試進階(分散式、效能調優、經典書籍pdf)資料等待領取