自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一篇文章讀懂Java類加載器

新聞 后端
Java類加載器算是一個(gè)老生常談的問(wèn)題,大多Java工程師也都對(duì)其中的知識(shí)點(diǎn)倒背如流,最近在看源碼的時(shí)候發(fā)現(xiàn)有一些細(xì)節(jié)的地方理解還是比較模糊,正好寫(xiě)一篇文章梳理一下。

Java類加載器算是一個(gè)老生常談的問(wèn)題,大多Java工程師也都對(duì)其中的知識(shí)點(diǎn)倒背如流,最近在看源碼的時(shí)候發(fā)現(xiàn)有一些細(xì)節(jié)的地方理解還是比較模糊,正好寫(xiě)一篇文章梳理一下。

關(guān)于Java類加載器的知識(shí),網(wǎng)上一搜一大片,我自己也看過(guò)很多文檔,博客。資料雖然很多,但還是希望通過(guò)本文盡量寫(xiě)出一些自己的理解,自己的東西。如果只是重復(fù)別人寫(xiě)的內(nèi)容那就失去寫(xiě)作的意義了。

類加載器結(jié)構(gòu)

類加載器結(jié)構(gòu)

名稱解釋:

  1. 根類加載器,也叫引導(dǎo)類加載器、啟動(dòng)類加載器。由于它不屬于Java類庫(kù),這里就不說(shuō)它對(duì)應(yīng)的類名了,很多人喜歡稱BootstrapClassLoader。本文都稱之為根類加載器。 
    加載路徑:<JAVA_HOME>\lib
  2. 擴(kuò)展類加載器,對(duì)應(yīng)Java類名為ExtClassLoader,該類是sun.misc.Launcher的一個(gè)內(nèi)部類。 
    加載路徑:<JAVA_HOME>\lib\ext
  3. 應(yīng)用類加載器,對(duì)應(yīng)Java類名為AppClassLoader,該類是sun.misc.Launcher的一個(gè)內(nèi)部類。 
    加載路徑:用戶目錄
//可以通過(guò)這種方式打印加載路徑
System.out.println("boot:"+System.getProperty("sun.boot.class.path"));
System.out.println("ext:"+System.getProperty("java.ext.dirs"));
System.out.println("app:"+System.getProperty("java.class.path"));

重點(diǎn)說(shuō)明:

  1. 根類加載器對(duì)于普通Java工程師來(lái)講可以理解成一個(gè)概念上的東西,因?yàn)槲覀儫o(wú)法通過(guò)Java代碼獲取到根類加載器,它屬于JVM層面。
  2. 除了根類加載器之外,其他兩個(gè)擴(kuò)展類加載器和應(yīng)用類加載器都是通過(guò)類sun.misc.Launcher進(jìn)行初始化,而Launcher類則由根類加載器進(jìn)行加載。

看下Launcher初始化源碼:

public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            //初始化擴(kuò)展類加載器,注意這里構(gòu)造函數(shù)沒(méi)有入?yún)?,即無(wú)法獲取根類加載器
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            //初始化應(yīng)用類加載器,注意這里的入?yún)⒕褪菙U(kuò)展類加載器
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        //設(shè)置上下文類加載器,這個(gè)后面會(huì)詳細(xì)說(shuō)
        Thread.currentThread().setContextClassLoader(this.loader);

       //刪除了一些安全方面的代碼
       //...
}

雙親委派模型

雙親委派模型是指當(dāng)我們調(diào)用類加載器的loadClass方法進(jìn)行類加載時(shí),該類加載器會(huì)首先請(qǐng)求它的父類加載器進(jìn)行加載,依次遞歸。如果所有父類加載器都加載失敗,則當(dāng)前類加載器自己進(jìn)行加載操作。

邏輯很簡(jiǎn)單,通過(guò)ClassLoader類的源碼來(lái)分析一下。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        //進(jìn)行類加載操作時(shí)首先要加鎖,避免并發(fā)加載
        synchronized (getClassLoadingLock(name)) {
            //首先判斷指定類是否已經(jīng)被加載過(guò)
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //如果當(dāng)前類沒(méi)有被加載且父類加載器不為null,則請(qǐng)求父類加載器進(jìn)行加載操作
                        c = parent.loadClass(name, false);
                    } else {
                       //如果當(dāng)前類沒(méi)有被加載且父類加載器為null,則請(qǐng)求根類加載器進(jìn)行加載操作
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }

                if (c == null) {
                    long t1 = System.nanoTime();
                    //如果父類加載器加載失敗,則由當(dāng)前類加載器進(jìn)行加載,
                    c = findClass(name);

                    //進(jìn)行一些統(tǒng)計(jì)操作
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            //初始化該類
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

雙親委派模型的實(shí)現(xiàn)邏輯總體看還是非常簡(jiǎn)單明了的。

這里有幾個(gè)細(xì)節(jié)需要說(shuō)明:

  1. ClassLoader類是一個(gè)抽象類,但卻沒(méi)有包含任何抽象方法。
  2. 如果要實(shí)現(xiàn)自己的類加載器且不破壞雙親委派模型,只需要繼承ClassLoader類并重寫(xiě)findClass方法。
  3. 如果要實(shí)現(xiàn)自己的類加載器且破壞雙親委派模型,則需要繼承ClassLoader類并重寫(xiě)loadClass,findClass方法。

令人疑惑的系統(tǒng)類加載器

當(dāng)你把上面的知識(shí)都搞清楚以后,會(huì)發(fā)現(xiàn)ClassLoader類中有個(gè)方法是getSystemClassLoader,系統(tǒng)類加載器,這又是什么?

系統(tǒng)類加載器是個(gè)容易讓人混淆的概念,我一度以為它就是應(yīng)用類加載器的別名,就跟啟動(dòng)類加載器和根類加載器道理一樣。事實(shí)上, 默認(rèn)情況下我們通過(guò)ClassLoader.getSystemClassLoader()獲取到的系統(tǒng)類加載器也確實(shí)是應(yīng)用類加載器 。

很多資料在說(shuō)類加載器結(jié)構(gòu)的時(shí)候會(huì)直接把應(yīng)用類加載器說(shuō)成是系統(tǒng)類加載器,其實(shí)我們通過(guò)類名就可以判斷兩個(gè)不是一回事。

系統(tǒng)類加載器可以通過(guò)System.setProperty("java.system.class.loader", xxx類名)進(jìn)行自定義設(shè)置。

系統(tǒng)類加載器不是一個(gè)全新的加載器,它只是一個(gè)概念,本質(zhì)上還是上述說(shuō)的四大類加載器(把用戶自定義類加載器算進(jìn)去),至于提出這個(gè)概念的原因以及使用場(chǎng)景,還需要繼續(xù)考究。

被人忽略的上下文類加載器

上面討論了各個(gè)類加載器的加載路徑。鑒于雙親委派模型的設(shè)計(jì),子類加載器都保留了父類加載器的引用,也就是說(shuō)當(dāng)由子類加載器加載的類需要訪問(wèn)由父類加載器加載的類時(shí),毫無(wú)疑問(wèn)是可以訪問(wèn)到的。但考慮一種場(chǎng)景,會(huì)不會(huì)有 父類加載器加載的類需要訪問(wèn)子類加載器加載的類 這種情況?如果有,怎么解決(父類加載器并沒(méi)有子類加載器的引用)?

這就是我們要討論的常常被人們忽略的上下文類加載器。

經(jīng)典案例:

JDBC是Java制定的一套訪問(wèn)數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)接口,它包含在Java基礎(chǔ)類庫(kù)中,也就是說(shuō)它是由根類加載器加載的。與此同時(shí),各個(gè)數(shù)據(jù)庫(kù)廠商會(huì)各自實(shí)現(xiàn)這套接口來(lái)讓Java工程師可以訪問(wèn)自己的數(shù)據(jù)庫(kù),而這部分實(shí)現(xiàn)類庫(kù)是需要Java工程師在工程中作為一個(gè)第三方依賴引入使用的,也就是說(shuō)這部分實(shí)現(xiàn)類庫(kù)是由應(yīng)用類加載器進(jìn)行加載的。

先上一段Java獲取Mysql連接的代碼:

//加載驅(qū)動(dòng)程序
Class.forName("com.mysql.jdbc.Driver");
//連接數(shù)據(jù)庫(kù)
Connection conn = DriverManager.getConnection(url, user, password);

這里DriverManager類就屬于Java基礎(chǔ)類庫(kù),由根類加載器加載。我們可以通過(guò)它獲取到數(shù)據(jù)庫(kù)的連接,顯然是它通過(guò)com.mysql.jdbc.Driver驅(qū)動(dòng)成功連接到了數(shù)據(jù)庫(kù),上面也說(shuō)了數(shù)據(jù)庫(kù)驅(qū)動(dòng)(作為第三方類庫(kù)引入)是由應(yīng)用類加載器加載的。這個(gè)場(chǎng)景就是典型的由父類加載器加載的類需要訪問(wèn)由子類加載器加載的類。

Java是怎么實(shí)現(xiàn)這種逆向訪問(wèn)的呢?直接看DriverManager類的源碼:

//建立數(shù)據(jù)庫(kù)連接各個(gè)不同參數(shù)的方法最終都會(huì)走到這里
private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        //獲取調(diào)用者的類加載器
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            //如果為null,則使用上下文類加載器
            //這里是重點(diǎn),什么時(shí)候類加載器才會(huì)為null? 當(dāng)然就是由根類加載器加載的類了
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        //...省略

        for(DriverInfo aDriver : registeredDrivers) {
            //使用上下文類加載器去加載驅(qū)動(dòng)
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    //如果加載成功,則進(jìn)行連接
                    Connection con = aDriver.driver.connect(url, info);
                    //...
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
            } 
            //...
        }
    }

重點(diǎn)說(shuō)明:

為什么上下文類加載器就可以加載到數(shù)據(jù)庫(kù)驅(qū)動(dòng)呢?回到上面一開(kāi)始Launcher初始化類加載器的源碼,我們發(fā)現(xiàn)原來(lái)所謂的上下文類加載器本質(zhì)上就是應(yīng)用類加載器,有沒(méi)有豁然開(kāi)朗的感覺(jué)? 上下文類加載器只是為了解決類的逆向訪問(wèn)提出來(lái)的一個(gè)概念,并不是一個(gè)全新的類加載器,它本質(zhì)上就是應(yīng)用類加載器 。

基本上我理解的Java類加載器就這么多知識(shí),如果有沒(méi)提到的或者是錯(cuò)誤的地方,歡迎交流。

責(zé)任編輯:張燕妮 來(lái)源: 簡(jiǎn)書(shū)
相關(guān)推薦

2019-09-24 14:19:12

PythonC語(yǔ)言文章

2017-06-08 22:41:34

框架標(biāo)簽

2018-04-09 16:35:10

數(shù)據(jù)庫(kù)MySQLInnoDB

2020-04-22 13:27:20

數(shù)據(jù)分析模塊解決

2021-05-09 09:06:24

Python批處理命令

2015-10-22 14:32:44

微服務(wù)PaaS應(yīng)用開(kāi)發(fā)

2020-10-09 08:15:11

JsBridge

2023-05-08 08:21:15

JavaNIO編程

2021-07-01 10:01:16

JavaLinkedList集合

2021-05-18 09:00:28

Pythonclass

2017-09-05 08:52:37

Git程序員命令

2022-02-21 09:44:45

Git開(kāi)源分布式

2023-05-12 08:19:12

Netty程序框架

2021-06-30 00:20:12

Hangfire.NET平臺(tái)

2019-04-17 15:16:00

Sparkshuffle算法

2024-06-25 08:18:55

2021-04-09 08:40:51

網(wǎng)絡(luò)保險(xiǎn)網(wǎng)絡(luò)安全網(wǎng)絡(luò)風(fēng)險(xiǎn)

2014-05-14 11:15:02

歷史起源iOSAndroid

2017-12-20 10:08:07

數(shù)據(jù)庫(kù)阿里巴巴分庫(kù)分表技術(shù)

2021-04-15 05:53:35

C# 索引器對(duì)象
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)