JVM雙親委派模型及SPI實現(xiàn)原理是什么

這篇文章主要講解了“JVM雙親委派模型及SPI實現(xiàn)原理是什么”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“JVM雙親委派模型及SPI實現(xiàn)原理是什么”吧!

為企業(yè)提供網(wǎng)站建設(shè)、網(wǎng)站制作、網(wǎng)站優(yōu)化、網(wǎng)絡(luò)營銷推廣、競價托管、品牌運營等營銷獲客服務(wù)。創(chuàng)新互聯(lián)建站擁有網(wǎng)絡(luò)營銷運營團(tuán)隊,以豐富的互聯(lián)網(wǎng)營銷經(jīng)驗助力企業(yè)精準(zhǔn)獲客,真正落地解決中小企業(yè)營銷獲客難題,做到“讓獲客更簡單”。自創(chuàng)立至今,成功用技術(shù)實力解決了企業(yè)“網(wǎng)站建設(shè)、網(wǎng)絡(luò)品牌塑造、網(wǎng)絡(luò)營銷”三大難題,同時降低了營銷成本,提高了有效客戶轉(zhuǎn)化率,獲得了眾多企業(yè)客戶的高度認(rèn)可!

1、雙親委派模型

我們知道類加載機制是將?個類從字節(jié)碼?件轉(zhuǎn)化為虛擬機可以直接使?類的過程,但是是誰來執(zhí)?這個過程中的加載過程,它?是如何完成或者說保障了類加載的準(zhǔn)確性和安全性呢?

答案就是類加載器以及雙親委派機制。

雙親委派模型的?作機制是:當(dāng)類加載器接收到類加載的請求時,它不會??去嘗試加載這個類,?是把這個請求委派給?加載器去完成,只有當(dāng)?類加載器反饋???法完成這個加載請求時,?加載器才會嘗試??去加載類。

我們可以從 JDK 源碼中將它的?作機制?窺究竟。

ClassLoader#loadClass(String, boolean)

這是在 jdk1.8 的 java.lang.ClassLoader 類中的源碼,這個?法就是?于加載指定的類。

/**
 * @author 庭前云落
 * @Date 2020/8/22 21:29
 * @description
 */
public class ClassLoader {
    protected Class<?> loadClass(String name, boolean resolve) throws
            ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            // ?先,檢查該類是否已經(jīng)被當(dāng)前類加載器加載,若當(dāng)前類加載未加載過該類,調(diào)??類的加載類?法去加載該類(如果?類為null的話交給啟動類加載器加載)
            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 foun
                    // from the non-null parent class loader }
                    if (c == null) {
                        // If still not found, then invoke findClass in orde
                        // to find the class.
                        // 如果?類未完成加載,使?當(dāng)前類加載器去加載該類
                        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;
            }
        }
    }

看完了上?的代碼,我們知道這就是雙親委派模型代碼層?的解釋:

  1. 當(dāng)類加載器接收到類加載的請求時,?先檢查該類是否已經(jīng)被當(dāng)前類加載器加載;

  2. 若該類未被加載過,當(dāng)前類加載器會將加載請求委托給?類加載器去完成;

  3. 若當(dāng)前類加載器的?類加載器(或?類的?類……向上遞歸)為 null,會委托啟動類加載器完成加 載;

  4. 若?類加載器?法完成類的加載,當(dāng)前類加載器才會去嘗試加載該類。

2、類加載器的分類及各自的職責(zé)

在 JVM 中預(yù)定義的類加載器有3種: 啟動類加載器(Bootstrap ClassLoader)、擴展類加載器(ExtensionClassLoader)、 應(yīng)?類/系統(tǒng)類加載器(App/SystemClassLoader),另外還有?種是 用戶自定義的類加載器,它們各?有各?的職責(zé)。

2.1.啟動類加載器 Bootstrap ClassLoader

啟動類加載器作為所有類加載器的"?祖宗",是由C++實現(xiàn)的,不繼承于java.lang.ClassLoader 類。它在虛擬機啟動時會由虛擬機的?段C++代碼進(jìn)?加載,所以它沒有?類加載器,在加載完成后,它會負(fù)責(zé)去加載擴展類加載器和應(yīng)?類加載器。

啟動類加載器?于加載 Java 的核?類——位于 <JAVA_HOME>\lib 中,或者被 -Xbootclasspath 參數(shù)所指定的路徑中,并且是虛擬機能夠識別的類庫(僅按照?件名識別,如 rt.jar、tools.jar ,名字不符合的類庫即使放在lib?錄中也不會被加載)。

2.2.拓展類加載器 Extension ClassLoader

拓展類加載器繼承于 java.lang.ClassLoader 類,它的?類加載器是啟動類加載器,?啟動類加載器在 Java 中的顯示就是 null。

引? jdk1.8 ClassLoader#getParent() ?法的注釋,這個?法是?于獲取類加載器的?類加載器: Returns the parent class loader for delegation. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class loader's parent is the bootstrap class loader.

拓展類加載器負(fù)責(zé)加載 <JAVA_HOME>\lib\ext ?錄中的,或者被 java.ext.dirs 系統(tǒng)變量所指定的路徑的所有類。

需要注意的是擴展類加載器僅?持加載被打包為 .jar 格式的字節(jié)碼?件。

2.3.應(yīng)?類/系統(tǒng)類加載器 App/System ClassLoader

應(yīng)?類加載器繼承于 java.lang.ClassLoader 類,它的?類加載器是擴展類加載器。

應(yīng)?類加載器負(fù)責(zé)加載?戶類路徑 classpath 上所指定的類庫。

如果應(yīng)?程序中沒有?定義的類加載器,?般情況下應(yīng)?類加載器就是程序中默認(rèn)的類加載器。

2.4.?定義類加載器 Custom ClassLoader

?定義類加載器繼承于 java.lang.ClassLoader 類,它的?類加載器是應(yīng)?類加載器。

這是普某戶籍?定義的類加載器,可加載指定路徑的字節(jié)碼?件。

?定義類加載器需要繼承 java.lang.ClassLoader 類并重寫 findClass ?法(下?有說明為什么不重寫 loadClass ?法)?于實現(xiàn)?定義的加載類邏輯。

3、雙親委派模型的好處

  1. 基于雙親委派模型規(guī)定的這種帶有優(yōu)先級的層次性關(guān)系,虛擬機運?程序時就能夠避免類的重復(fù)加載。 當(dāng)?類類加載器已經(jīng)加載過類時,如果再有該類的加載請求傳遞到?類類加載器,?類類加載器執(zhí)? loadClass ?法,然后委托給?類類加載器嘗試加載該類,但是?類類加載器執(zhí)? Class<?> c = findLoadedClass(name); 檢查該類是否已經(jīng)被加載過這?階段就會檢查到該類已經(jīng)被加載過,直接返回該類,?不會再次加載此類。

  2. 雙親委派模型能夠避免核?類篡改。?般我們描述的核?類是 rt.jar、tools.jar 這些由啟動類加載器加載的類,這些類庫在?常開發(fā)中被?泛運?,如果被篡改,后果將不堪設(shè)想。 假設(shè)我們?定義了?個 java.lang.Integer 類,與好處1?樣的流程,當(dāng)加載類的請求傳遞到啟動類加載器時,啟動類加載器執(zhí)行findLoadedClass(String) ?法發(fā)現(xiàn) java.lang.Integer 已經(jīng)被加載過,然后直接返回該類,加載該類的請求結(jié)束。雖然避免核?類被篡改這?點的原因與避免類的重復(fù)加載?致,但這還是能夠作為雙親委派模型的好處之?的。

4、雙親委派模型的不足

這?所說的不?也可以理解為打破雙親委派模型,當(dāng)雙親委派模型不滿??戶需求時,?然是由于其不?之處也就促使?戶將其打破這?描述的也就是打破雙親委派模型的三種

  1. 由于歷史原因( ClassLoader 類在 JDK1.0 時就已經(jīng)存在,?雙親委派模型是在 JDK1.2 之后才引?的),在未引?雙親委派模型時,?戶自定義的類加載器需要繼承 java.lang.ClassLoader 類并重寫 loadClass() 方法,因為虛擬機在加載類時會調(diào)? ClassLoader#loadClassInternal(String) ,而這個?法(源碼如下)會調(diào)?自定義類加載重寫的 loadClass() 方法。

而在引?雙親委派模型后, ClassLoader#loadClass ?法實際就是雙親委派模型的實現(xiàn),如果重寫了此?法,相當(dāng)于打破了雙親委派模型。為了讓?戶?定義的類加載器也遵從雙親委派模型, JDK新增了 findClass 方法,?于實現(xiàn)?定義的類加載邏輯。

/**
 * @author 庭前云落
 * @Date 2020/8/22 21:29
 * @description
 */
public class ClassLoader {
    // This method is invoked by the virtual machine to load a class.
    private Class<?> loadClassInternal(String name) throws
            ClassNotFoundException {
        // For backward compatibility, explicitly lock on 'this' when
        // the current class loader is not parallel capable.
        if (parallelLockMap == null) {
            synchronized (this) {
                return loadClass(name);
            }
        } else {
            return loadClass(name);
        }
    }
// 其余?法省略......
}
  1. 由于雙親委派模型規(guī)定的層次性關(guān)系,導(dǎo)致?類類加載器加載的類能訪問?類類加載器加載的類,??類類加載器加載的類?法訪問?類類加載器加載的類。為了讓上層類加載器加載的類能夠訪問下層類加載器加載的類,或者說讓?類類加載器委托?類類加載器完成加載請求,JDK 引?了線程上下?類加載器,藉由它來打破雙親委派模型的屏障。

  2. 當(dāng)?戶需要程序的動態(tài)性,?如代碼熱替換、模塊熱部署等時,雙親委派模型就不再適?,類加載器會發(fā)展為更為復(fù)雜的?狀結(jié)構(gòu)。

5、線程上下文類加載器

上?說到雙親委派模型的不?時提到了線程上下?類加載器 Thread Context ClassLoader ,線程上下?類加載器是定義在 Thread 類中的?個 ClassLoader 類型的私有成員變量,它指向了當(dāng)前線程的類加載器。

上?已經(jīng)提到線程上下?類加載能夠讓?類類加載器委托?類類加載器完成加載請求,那么這是如何實現(xiàn)的呢?下?就來討論?下。

5.1.SPI 在 JDBC 中的應(yīng)用

我們知道 Java 提供了?些 SPI(Service Provider Interface) 接?,它允許服務(wù)商編寫具體的代碼邏輯來完成該接?的功能。

但是 Java 提供的 SPI 接?是在核?類庫中,由啟動類加載器加載的,?商實現(xiàn)的具體邏輯代碼是在classpath 中,是由應(yīng)?類加載器加載的,?啟動類加載器加載的類?法訪問應(yīng)?類加載器加載的類,也就是說啟動類加載器?法找到 SPI 實現(xiàn)類,單單依靠雙親委派模型就?法實現(xiàn) SPI 的功能了,所以線程上下?類加載器應(yīng)運??。

在 Java 提供的 SPI 中我們最常?的可能就屬 JDBC 了,下?我們就以 JDBC 為例來看?下線程上下?類加載器如何打破雙親委派模型。

回憶?下以前使? JDBC 的場景,我們需要創(chuàng)建驅(qū)動,然后創(chuàng)建連接,就像下?的代碼這樣:

/**
 * @author 庭前云落
 * @Date 2020/8/22 21:29
 * @description
 */
public class ThreadContextClassLoaderDemoOfJdbc {
    public static void main(String[] args) throws Exception {
        // 加載 Driver 的實現(xiàn)類
        Class.forName("com.MySQL.jdbc.Driver");
        // 建?連接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "1234");
    }
}

在 JDK1.6 以后可以不?寫 Class.forName("com.mysql.jdbc.Driver"); ,代碼依舊能正常運?。這是因為?帶的 jdbc4.0 版本已?持 SPI 服務(wù)加載機制,只要服務(wù)商的實現(xiàn)類在classpath 路徑中,Java 程序會主動且?動去加載符合 SPI 規(guī)范的具體的驅(qū)動實現(xiàn)類,驅(qū)動的全限定類名在 META-INF.services ?件中。

所以,讓我們把?光聚焦于建?連接的語句,這?調(diào)?了 DriverManager 類的靜態(tài)?法getConnection 。

在調(diào)?此?法前,根據(jù)類加載機制的初始化時機,調(diào)?類的靜態(tài)?法會觸發(fā)類的初始化,當(dāng) DriverManager 類被初始化時,會執(zhí)?它的靜態(tài)代碼塊。

/**
 * @author 庭前云落
 * @Date 2020/8/22 21:29
 * @description
 */
public class DriverManager {
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

    private static void loadInitialDrivers() {
        String drivers;
        // 省略代碼:?先讀取系統(tǒng)屬性 jdbc.drivers

        // 通過 SPI 加載 classpath 中的驅(qū)動類
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                // ServiceLoader 類是 SPI 加載的?具類
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try {
                    while (driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch (Throwable t) {
                    // Do nothing
                }
                return null;
            }
        });
        // 省略代碼:使?應(yīng)?類加載器繼續(xù)加載系統(tǒng)屬性 jdbc.drivers 中的驅(qū)動類
    }
}

從上?的代碼中可以看到,程序時通過調(diào)? ServiceLoader.load(Driver.class) ?法來完成?動加載。classpath 路徑中具體的所有實現(xiàn)了 Driver.class 接?的?商實現(xiàn)類,?在 ServiceLoader.load() 方法中,就是獲取了當(dāng)前線程上下?類加載器,并將它傳遞下去,將它作為類加載器去實現(xiàn)加載邏輯的。

/**
 * @author 庭前云落
 * @Date 2020/8/22 21:29
 * @description
 */
public final class ServiceLoader<S> implements Iterable<S>{
    public static <S> ServiceLoader<S> load(Class<S> service) {
        // 獲取當(dāng)前線程的線程上下?類加載器 AppClassLoader,?于加載 classpath 中的具體實現(xiàn)類
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
}

JDK 默認(rèn)加載當(dāng)前類的類加載器去加載當(dāng)前類所依賴且未被加載的類,而ServiceLoader 類位于java.util 包下,?然是由啟動類加載器完成加載,而廠商實現(xiàn)的具體驅(qū)動類是位于 classpath 下,啟動類加載器?法加載 classpath 目錄的類,而如果加載具體驅(qū)動類的類加載器變成了應(yīng)?類加載器,那么就可以完成加載了。

通過跟蹤代碼,不難看出 ServiceLoader#load(Class) ?法創(chuàng)建了?個 LazyIterator 類同時返回了?個 ServiceLoader 對象,前者是?個懶加載的迭代器,同時它也是后者的?個成員變量,當(dāng)對迭代器進(jìn)行遍歷時,就觸發(fā)了?標(biāo)接?實現(xiàn)類的加載。

/**
 * @author 庭前云落
 * @Date 2020/8/22 21:29
 * @description
 */
private class LazyIterator implements Iterator<S> {
    public S next() {
        if (acc == null) {
            return nextService();
        } else {
            PrivilegedAction<S> action = new PrivilegedAction<S>() {
                public S run() { return nextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }
}

在 DriverManager#loadInitialDrivers ?法,也就是 DriverManager 類的靜態(tài)代碼塊所執(zhí)?的?法中,有這樣?段代碼:

AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run () {
        ServiceLoader<Driver> loadedDrivers =
                ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator =
                loadedDrivers.iterator();
        try {
            while (driversIterator.hasNext()) {
                driversIterator.next();
            }
        } catch (Throwable t) {
            // Do nothing
        }
        return null;
    }
 });

這段代碼返回了?個 ServiceLoader 對象,在這個對象中有?個 LazyIterator 迭代器類,?于存放所有?商實現(xiàn)的具體驅(qū)動類,當(dāng)我們對 LazyIterator 這個迭代器進(jìn)?遍歷時,就出發(fā)了類加載的邏輯。

private S nextService() {
        if (!hasNextService())
            throw new NoSuchElementException();
        String cn = nextName;
        nextName = null;
        Class<?> c = null;
        try {
            // 不?寫 Class.forName("com.mysql.jdbc.Driver"); 的原因就是在此處會?動調(diào)?這個?法
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service, "Provider " + cn + " not found");
        }
        if (!service.isAssignableFrom(c)) {
            fail(service, "Provider " + cn + " not a subtype");
        }
        try {
            S p = service.cast(c.newInstance());
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service, "Provider " + cn + " could not be instantiated", x);
        }
        throw new Error(); // This cannot happen
    }

每次遍歷都會調(diào)? Class.forName(cn, false, loader) ?法對指定的類進(jìn)?加載和實例化操作,這也是前?提到的在 jdk1.6 以后不?在寫 Class.forName("com.mysql.jdbc.Driver"); 的原因。

在這個?法 Class.forName(cn, false, loader) 中,傳?的參數(shù) cn 是全路徑類名,false 是指不進(jìn)?初始化,loader 則是指定完成 cn 類加載的類加載器。

在這?的 loader 變量,我們回顧?下前?的描述,在 ServiceLoader.load(Driver.class) ?法中是不是獲取了線程上下?類加載器并傳遞下去?

不記得?在回過頭去看?遍!

?傳?的線程上下?類加載器會作為參數(shù)傳遞給 ServiceLoader 類的構(gòu)造方法

   private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null ");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ?
                AccessController.getContext() : null;
        reload();

    }

?此處的 cl 變量就是調(diào)? DriverManager 類靜態(tài)?法的線程上下?類加載器,即應(yīng)?類加載器。

也就是說,通過 DriverManager 類的靜態(tài)?法,實現(xiàn)了由 ServiceLoader 類觸發(fā)加載位于 classpath的?商實現(xiàn)的驅(qū)動類。前?已經(jīng)說過, ServiceLoader 類位于 java.util 包中,是由啟動類加載器加載的,?由啟動類加載器加載的類竟然實現(xiàn)了"委派"應(yīng)?類加載器去加載驅(qū)動類,這?疑是與雙親委派機制相悖的。?實現(xiàn)這個功能的,就是線程上下?類加載器。

感謝各位的閱讀,以上就是“JVM雙親委派模型及SPI實現(xiàn)原理是什么”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對JVM雙親委派模型及SPI實現(xiàn)原理是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

本文題目:JVM雙親委派模型及SPI實現(xiàn)原理是什么
分享路徑:http://www.muchs.cn/article2/ijssoc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號、響應(yīng)式網(wǎng)站、靜態(tài)網(wǎng)站、服務(wù)器托管、Google、商城網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

綿陽服務(wù)器托管