類加載階段分爲加載、鏈接、初始化三個階段,而加載階段須要經過類的全限定名來獲取定義了此類的二進制字節流。Java特地把這一步抽出來用類加載器來實現。把這一步驟抽離出來使得應用程序能夠按需自定義類加載器。而且得益於類加載器,OSGI、熱部署等領域才得以在JAVA中獲得應用。java
在Java中任意一個類都是由這個類自己和加載這個類的類加載器來肯定這個類在JVM中的惟一性。也就是你用你A類加載器加載的com.aa.ClassA
和你A類加載器加載的com.aa.ClassA
它們是不一樣的,也就是用instanceof
這種對比都是不一樣的。因此即便都來自於同一個class文件可是由不一樣類加載器加載的那就是兩個獨立的類。mysql
類加載器除了能用來加載類,還能用來做爲類的層次劃分。Java自身提供了3種類加載器面試
一、啓動類加載器(Bootstrap ClassLoader),它是屬於虛擬機自身的一部分,用C++實現的,主要負責加載<JAVA_HOME>\lib
目錄中或被-Xbootclasspath指定的路徑中的而且文件名是被虛擬機識別的文件。它等因而全部類加載器的爸爸。sql
二、擴展類加載器(Extension ClassLoader),它是Java實現的,獨立於虛擬機,主要負責加載<JAVA_HOME>\lib\ext
目錄中或被java.ext.dirs系統變量所指定的路徑的類庫。數據庫
三、應用程序類加載器(Application ClassLoader),它是Java實現的,獨立於虛擬機。主要負責加載用戶類路徑(classPath)上的類庫,若是咱們沒有實現自定義的類加載器那這玩意就是咱們程序中的默認加載器。bash
知道上面這幾個概念就能來看看雙親委派模型了。oracle
雙親委派的意思是若是一個類加載器須要加載類,那麼首先它會把這個類請求委派給父類加載器去完成,每一層都是如此。一直遞歸到頂層,當父加載器沒法完成這個請求時,子類纔會嘗試去加載。這裏的雙親其實就指的是父類,沒有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設計者推薦使用類加載器的方式。ide
雙親委派有啥好處呢?它使得類有了層次的劃分。就拿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)資料等待領取