圖解類加載器和雙親委派機制,一看就懂

據說微信搜索《Java魚仔》會變動強哦!java

本文收錄於JavaStarter ,裏面有我完整的Java系列文章,學習或面試均可以看看哦git

(一)概述

咱們都知道Java代碼會被編譯成class文件,在class文件中描述了該類的各類信息,class類最終須要被加載到虛擬機中才能運行和使用。github

虛擬機把Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成虛擬機能夠直接使用的Java類型,這就是虛擬機的類加載機制。面試

(二)類加載的過程

一個類從被加載到卸載出內存,一共包含下面七個階段:數據庫

加載、驗證、準備、解析、初始化、使用、卸載 在這裏插入圖片描述 加載的來源有如下部分:微信

一、本地磁盤網絡

二、網絡下載的.class文件app

三、war,jar下加載.class文件ide

四、從專門的數據庫中讀取.class文件(少見)學習

五、將java源文件動態編譯成class文件,典型的就是動態代理,經過運行時生成class文件

加載的過程是經過類加載器實現的。有關類加載的其餘過程我會在下一章中介紹。

(三)類加載器的分類

類加載器分爲系統級別和用戶級別:

系統級別的類加載器有:

一、啓動類加載器(底層使用C++實現)

二、擴展類加載器(底層使用java實現,是ClassLoader的子類)

三、應用程序類加載器(底層使用java實現,是ClassLoader的子類)

用戶級別的類加載器咱們統一稱爲自定義類加載器。

3.1 啓動類加載器

首先咱們來看看啓動類加載器加載了哪些類,啓動類加載器負責加載sun.boot.class.path:

public static void bootClassLoaderLoadingPath(){
    //獲取啓動列加載器加載的目錄
    String bootStrapLoadingPath=System.getProperty("sun.boot.class.path");
    //把加載的目錄轉爲集合
    List<String> bootLoadingPathList= Arrays.asList(bootStrapLoadingPath.split(";"));
    for (String bootPath:bootLoadingPathList){
        System.out.println("啓動類加載器加載的目錄:"+bootPath);
    }
}

經過上面的代碼咱們能夠獲取到啓動類加載器所加載的類:

在這裏插入圖片描述

3.2 拓展類加載器

擴展類加載器加載負責加載java.ext.dirs,咱們一樣寫一段代碼去加載它:

public static void extClassLoaderLoadingPath(){
    //獲取啓動列加載器加載的目錄
    String bootStrapLoadingPath=System.getProperty("java.ext.dirs");
    //把加載的目錄轉爲集合
    List<String> bootLoadingPathList= Arrays.asList(bootStrapLoadingPath.split(";"));
    for (String bootPath:bootLoadingPathList){
        System.out.println("拓展類加載器加載的目錄:"+bootPath);
    }
}

能夠看到,除了加載了JDK目錄下的ext外,還加載了Sun目錄下的ext

在這裏插入圖片描述

3.3 應用程序類加載器

最後是應用類加載器,它負責加載java.class.path:

public static void appClassLoaderLoadingPath(){
    //獲取啓動列加載器加載的目錄
    String bootStrapLoadingPath=System.getProperty("java.class.path");
    //把加載的目錄轉爲集合
    List<String> bootLoadingPathList= Arrays.asList(bootStrapLoadingPath.split(";"));
    for (String bootPath:bootLoadingPathList){
        System.out.println("應用程序類加載器加載的目錄:"+bootPath);
    }
}

它負責加載工程目錄下classpath下的class以及jar包。

(四)雙親委派模型

所謂雙親委派模型,就是指一個類接收到類加載請求後,會把這個請求依次傳遞給父類加載器(若是還有的話),若是頂層的父類加載器能夠加載,就成功返回,若是沒法加載,再依次給子加載器去加載。 在這裏插入圖片描述 咱們先經過代碼來看一下類加載器的層級結構:

public class ClassLoaderPath {
    public static void main(String[] args) {
        System.out.println(ClassLoaderPath.class.getClassLoader());
        System.out.println(ClassLoaderPath.class.getClassLoader().getParent());
        System.out.println(ClassLoaderPath.class.getClassLoader().getParent().getParent());
    }
}

編寫一個類,依次輸出這個類的類加載器,父類加載器,父類的父類加載器

在這裏插入圖片描述

能夠看到首先是應用程序類加載器,它的父類是擴展類加載器,擴展類加載器的父類輸出了一個null,這個null會去調用啓動類加載器。若是你不信,咱們看源碼:ClassLoader類

在這裏插入圖片描述

接着從父類加載器往下調用findClass,若是能夠加載,就直接返回class,若是不能加載,就依次向下。若是到了自定義加載器仍是沒法被加載,就會拋出ClassNotFound異常。

我畫了一個流程圖來展現雙親委派模型的全過程:

在這裏插入圖片描述

雙親委派模型保證了Java程序的穩定運行,能夠避免類的重複加載,也保證了 Java 的核心 API 不被篡改。

(五)破壞雙親委派

雙親委派模型並非絕對的,spi機制就能夠打破雙親委派模型。

首先咱們須要瞭解什麼是spi,spi(Service Provider Interface)是一種服務發現機制,Java在覈心庫中定義了許多接口,而且針對這些接口給出調用邏輯,可是並未給出具體的實現。開發者要作的就是定製一個實現類,在 META-INF/services 中註冊實現類信息,以供核心類庫使用。最典型的就是JDBC。

Java提供了一個Driver接口用於驅動各個廠商的數據庫鏈接,Driver類位於JAVA_HOME中jre/lib/rt.jar中,應該由Bootstrap類加載器進行加載。根據類加載機制,當被加載的類引用了另一個類的時候,虛擬機就會使用加載該類的類加載器加載被引用的類,所以若是其餘數據庫廠商定製了Driver的實現類以後,按理說也得把這個實現類放到啓動類加載器加載的目錄下,這顯然是很不合理的。

因而Java提供了spi機制,即便Driver由啓動類加載器去加載,可是他可讓線程上下文加載器(Thread Context ClassLoader)去請求子類加載器去完成加載,默認是應用程序類加載器。可是這確實破壞了類加載機制。

相關文章
相關標籤/搜索