1. 程式人生 > >java類載入器以及spi

java類載入器以及spi

類載入器概述:

每個編寫的”.java”拓展名類檔案都儲存著需要執行的程式邏輯,這些”.java”檔案經過Java編譯器編譯成拓展名為”.class”的檔案,”.class”檔案中儲存著Java程式碼經轉換後的虛擬機器指令,當需要使用某個類時,虛擬機器將會載入它的”.class”檔案,並建立對應的class物件,將class檔案載入到虛擬機器的記憶體,這個過程稱為類載入。
  那麼 .class 檔案什麼時候會被類載入器載入到 JVM 中那?比如執行 new 操作的時候,我們使用 Class.forName(“ 包路徑 + 類名 “)、Class.forName(“ 包路徑 + 類名 “,ClassLoader)、ClassLoader.loadClass(“ 包路徑 + 類名 “) 就觸發了類載入器去類載入對應的路徑去查詢 *.class,並建立 Class 物件。另外需要注意的是除去 new 操作外,其他幾種方式載入位元組碼到記憶體後只是生產一個 Class 物件,要產生具體的物件例項還需要使用 Class 物件 .newInstance() 函式來建立。

類的生命週期:

類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入、驗證、準備、解析、初始化、使用和解除安裝七個階段。它們開始的順序如下圖所示:
在這裡插入圖片描述

載入:

通過類的全限定名來獲取定義此類的二進位制位元組流

將此二進位制位元組流所代表的靜態儲存結構轉化成方法區的執行時資料結構

在堆記憶體中生成代表此類的java.lang.Class物件,作為該類訪問入口.
  在這裡插入圖片描述

驗證:

連線階段第一步.驗證的目的是確保Class檔案的位元組流中資訊符合虛擬機器的要求,不會危害虛擬機器安全,使得虛擬機器免受惡意程式碼的攻擊.大致完成以下四個校驗動作:

檔案格式驗證

源資料驗證

位元組碼驗證

符號引用驗證

準備:連線階段第二步,
正式為類變數分配記憶體並設定變數的初始值.(僅包含類變數,不包含例項變數).
解釋如下:  
為類變數(即static修飾的欄位變數)分配記憶體並且設定該類變數的初始值即0(如static int i=5;這裡只將i初始化為0,至於5的值將在初始化時賦值),這裡不包含用final修飾的static,因為final在編譯的時候就會分配了,注意這裡不會為例項變數分配初始化,類變數會分配在方法區中,而例項變數是會隨著物件一起分配到Java堆中。
預設初始值如下:

1.八種基本資料型別預設的初始值是0
2.引用型別預設的初始值是null
3.有static final修飾的會直接賦值,例如:static final int x=10;則預設就是10.
解析:連線階段第三步,


虛擬機器將常量池中的符號引用替換為直接引用,解析動作主要針對類或介面,欄位,類方法,方法型別等等…
說白了就是jvm會將所有的類或介面名、欄位名、方法名轉換為具體的記憶體地址。
初始化:
類的初始化是類載入過程的最後一步,在該階段,才真正意義上的開始執行類中定義的java程式程式碼.該階段會執行類構造器.
類載入最後階段,若該類具有超類,則對其進行初始化,執行靜態初始化器和靜態初始化成員變數(如前面只初始化了預設值的static變數將會在這個階段賦值,成員變數也將被初始化)。
使用:
使用該類所提供的功能.

解除安裝:
從記憶體中釋放.
該類所有的例項都已經被回收,也就是java堆中不存在該類的任何例項。
載入該類的ClassLoader已經被回收
該類對應的java.lang.Class物件沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法
類載入器

java.lang.classLoader

在這裡插入圖片描述
 大部分java程式會使用以下3中系統提供的類載入器:

啟動類載入器(Bootstrap ClassLoader):

這個類載入器負責將<JAVA_HOME>\lib目錄下的類庫載入到虛擬機器記憶體中,用來載入java的核心庫,此類載入器並不繼承於java.lang.ClassLoader,不能被java程式直接呼叫,程式碼是使用C++編寫的.是虛擬機器自身的一部分.

擴充套件類載入器(Extendsion ClassLoader):
    這個類載入器負責載入<JAVA_HOME>\lib\ext目錄下的類庫,用來載入java的擴充套件庫,開發者可以直接使用這個類載入器.

應用程式類載入器(Application ClassLoader):

這個類載入器負責載入使用者類路徑(CLASSPATH)下的類庫,一般我們編寫的java類都是由這個類載入器載入,這個類載入器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也稱為系統類載入器.一般情況下這就是系統預設的類載入器.

除此之外,我們還可以加入自己定義的類載入器,以滿足特殊的需求,需要繼承java.lang.ClassLoader類.
  在這裡插入圖片描述
  **

類載入器的雙親委派模型:

**
  「雙親委託模式」指的就是某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,如果父類載入器可以完成類載入任務,就成功返回;**只有父類載入器無法完成此載入任務時,自己才去載入。
   雙親委託模型的工作過程:如果一個類載入器收到了一個類載入請求,它首先不會自己去嘗試載入這個類,而是把這個請求委託給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求最終都應該傳送到頂層的啟動類載入器中,只有當父類載入器反饋自己無法完成載入請求(它的搜尋範圍之中沒有找到這個類)時,子載入器才會嘗試著自己去載入。
   在這裡插入圖片描述
**
  Class.forname()與ClassLoader.loadClass():
  Class.forname():是一個靜態方法,最常用的是Class.forname(String className);根據傳入的類的全限定名返回一個Class物件.該方法在將Class檔案載入到記憶體的同時,會執行類的初始化.

如: Class.forName(“com.wang.HelloWorld”);

ClassLoader.loadClass():這是一個例項方法,需要一個ClassLoader物件來呼叫該方法,該方法將Class檔案載入到記憶體時,並不會執行類的初始化,直到這個類第一次使用時才進行初始化.該方法因為需要得到一個ClassLoader物件,所以可以根據需要指定使用哪個類載入器.

如:ClassLoader cl=…;cl.loadClass(“com.wang.HelloWorld”);

Class.forname()與ClassLoader.loadClass():
  Class.forname():是一個靜態方法,最常用的是Class.forname(String className);根據傳入的類的全限定名返回一個Class物件.該方法在將Class檔案載入到記憶體的同時,會執行類的初始化.

如: Class.forName(“com.wang.HelloWorld”);

ClassLoader.loadClass():這是一個例項方法,需要一個ClassLoader物件來呼叫該方法,該方法將Class檔案載入到記憶體時,並不會執行類的初始化,直到這個類第一次使用時才進行初始化.該方法因為需要得到一個ClassLoader物件,所以可以根據需要指定使用哪個類載入器.

如:ClassLoader cl=…;cl.loadClass(“com.wang.HelloWorld”);

Class.forname()與ClassLoader.loadClass():
  Class.forname():是一個靜態方法,最常用的是Class.forname(String className);根據傳入的類的全限定名返回一個Class物件.該方法在將Class檔案載入到記憶體的同時,會執行類的初始化.

如: Class.forName(“com.wang.HelloWorld”);

ClassLoader.loadClass():這是一個例項方法,需要一個ClassLoader物件來呼叫該方法,該方法將Class檔案載入到記憶體時,並不會執行類的初始化,直到這個類第一次使用時才進行初始化.該方法因為需要得到一個ClassLoader物件,所以可以根據需要指定使用哪個類載入器.

如:ClassLoader cl=…;cl.loadClass(“com.wang.HelloWorld”);

(1)優點

避免類庫重複載入
安全,將核心類庫與使用者類庫隔離,使用者不能通過載入器替換核心類庫,如String類。
(2)弊端

委託永遠是子載入器去請求父載入器,是單向的,即上層的類載入器無法訪問下層的類載入器所載入的類:
舉個例子,假設「BootStrap」中提供了一個介面,及一個建立其例項的工廠方法,但是該介面的實現類在「System」中,那麼就會出現工廠方法無法建立在「System」載入的類的例項的問題。擁有這樣問題的元件有很多,比如JDBC、Xml parser等。

3、如何解決弊端——使用「SPI」

「SPI」 全稱為 (Service Provider Interface) ,是JDK內建的一種服務提供發現機制。 目前有不少框架用它來做服務的擴充套件發現, 簡單來說,它就是一種動態替換髮現的機制。
API和SPI的區別
API 直接被應用開發人員使用,SPI 被框架擴充套件人員使用
API Application Programming Interface

大多數情況下,都是實現方來制定介面並完成對介面的不同實現,呼叫方僅僅依賴卻無權選擇不同實現。

SPI Service Provider Interface

而如果是呼叫方來制定介面,實現方來針對介面來實現不同的實現。呼叫方來選擇自己需要的實現方。

3、JDBC舉例

下面以JDBC為例,介紹「SPI」機制。

在JDBC4.0之前,我們開發有連線資料庫的時候,通常會用Class.forName(“com.mysql.jdbc.Driver”)這句先載入資料庫相關的驅動,然後再進行獲取連線等的操作。而JDBC4.0之後不需要用Class.forName(“com.mysql.jdbc.Driver”)來載入驅動,直接獲取連線就可以了,現在這種方式就是使用了Java的「SPI」擴充套件機制來實現。
jdbc的獲取連線的兩種方式
新增連結描述

(1)介面定義

JDBC在java.sql.Driver只定義了介面。
在這裡插入圖片描述
(2)廠商實現

這裡以MySQL為例,在mysql-connector-java-5.1.39.jar包裡的META-INF/services目錄下可以找到一個java.sql.Driver檔案,檔案內容是一個類名,這個名叫com.mysql.cj.jdbc.Driver的類就是MySQL針對JDBC中定義的介面的實現。
在這裡插入圖片描述
在這裡插入圖片描述
(3)如何使用

在我們的應用裡面,我們就可以直接連線MySQL了。

Connection conn = DriverManager.getConnection(url,username,password);
顯然語句並沒有載入實現類,這裡就涉及到使用「SPI」擴充套件機制來查詢相關驅動了,接下來,我們結合原始碼探究一下這是如何實現的。

4、原始碼解析
關於驅動的查詢其實都在DriverManager中,DriverManager位於java.sql包裡,用來獲取資料庫連線,在DriverManager中有一個靜態程式碼塊如下:
static {
loadInitialDrivers();
println(“JDBC DriverManager initialized”);
}
loadInitialDrivers方法用於例項化驅動,由3部分構成:
(1)獲取有關驅動的名稱
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction() {
public String run() {
return System.getProperty(“jdbc.drivers”);
}
});
} catch (Exception ex) {
drivers = null;
}
drivers儲存驅動的定義
(2)載入並例項化驅動
在這裡插入圖片描述
比較關鍵的地方是ServiceLoader.load
其中ServiceLoader.load(Driver.class)方法原始碼:
public static ServiceLoader load(Class service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

   java的雙親委託類載入機制(ClassLoaderA -> System class loader -> Extension class loader -> Bootstrap class loader)可以保證核心類的正常安全載入。但是右邊的 Bootstrap class loader 所載入的程式碼需要反過來去找委派鏈靠左邊的 ClassLoader A 去載入東西的時候,就需要委派鏈左邊的 ClassLoader 設定為執行緒的上下文載入器即可。

每一個執行緒都有自己的ContextClassLoader,預設以SystemClassLoader為ContextClassLoader。通過Thread.currentThread().getContextClassLoader(),可以把一個ClassLoader置於一個執行緒的例項之中,使該ClassLoader成為一個相對共享的例項,這樣即使是啟動類載入器中的程式碼也可以通過這種方式訪問應用類載入器中的類了。
參考連結:
https://www.cnblogs.com/hiyujie/p/wo-xueJava1ClassLoader-yu-shuang-qin-wei-tuo-mo-sh.html