Java類加載器

本文和大家聊聊Java類加載器這檔子事。

什么是類加載器?

咱們先來給他下一個(gè)通俗點(diǎn)的定義:

將java字節(jié)碼(.class文件)轉(zhuǎn)換成類對象(java.lang.Class),加載到JVM內(nèi)存。

Java中有哪幾種類加載器?

類加載器可分為四種:
Bootstrap ClassLoader:由C++代碼實(shí)現(xiàn),加載sun.boot.class.path所指定的目錄,或<JAVA_HOME>\lib目錄中文件。

Ext ClassLoader:加載系統(tǒng)變量 java.ext.dirs所指定的目錄,或<JAVA_HOME>\lib\ext 目錄中的文件。

App ClassLoader:加載用戶類路徑下的文件。

Custom ClassLoader:開發(fā)者自己折騰的類加載器,按需進(jìn)行類文件加載。(例如:網(wǎng)絡(luò)類加載器,通過網(wǎng)絡(luò)加載.class文件)

類加載器如何工作?

第一步,類加載器向JVM查詢目標(biāo)對象(請求加載的對象)是否已經(jīng)加載過,若已經(jīng)加載過,則直接返回此對象;否則,進(jìn)行第二步。

第二步,獲得當(dāng)前類加載器的父類加載器,遞歸執(zhí)行這一步驟,由下往上,直至到達(dá)頂級父類加載器(即Bootstrap ClassLoader),進(jìn)行第三步。

第三步,從頂級父類加載器,由上而下,在自己的搜索范圍內(nèi)檢索目標(biāo)對象的類文件(.class),若檢索到,則讀取該文件的字節(jié)碼轉(zhuǎn)換成Class對象到JVM,返回目標(biāo)對象;否則,拋出ClassNotFoundException。

有讀者老爺說了,聽你白扯了半天,對類加載器的認(rèn)知還是不清楚,有沒有實(shí)質(zhì)性的東西?下面咱們來點(diǎn)實(shí)的。

類加載器的委派模式

從上文中類加載器的工作步驟,可以看出,遞歸獲取父類加載器,由下往上,直達(dá)頂級父類加載器,在從頂級父類加載器,自上而下,逐級在自己的搜索范圍內(nèi)檢索目標(biāo)對象類文件,轉(zhuǎn)換成Class對象(若檢索到)的這種行為,稱為類加載器的委派模式

描述遞歸獲取父類加載器,由下往上,直達(dá)頂級父類加載器的代碼如下:

//代碼段截取自ClassLoader.loadClass(String name, boolean resolve)
 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 found
                  // from the non-null parent class loader
           }

描述從頂級父類加載器,自上而下,逐級在自己的搜索范圍內(nèi)檢索目標(biāo)對象類文件,轉(zhuǎn)換成Class對象的代碼如下:

 //代碼段截取自ClassLoader.loadClass(String name, boolean resolve)
   if (c == null) {
      // If still not found, then invoke findClass in order
      // to find the class.
      long t1 = System.nanoTime();
      c = findClass(name);//在自己的搜索范圍內(nèi)進(jìn)行檢索,若找到,轉(zhuǎn)換成Class對象
       .....................................
       .....................................
      }
      if (resolve) {
            resolveClass(c);
       }
       return c;

真正實(shí)現(xiàn)類加載的是defineClass方法,代碼如下:

//代碼段截取自URLClassLoader.findClass(final String name)
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
   try {
          return defineClass(name, res); //執(zhí)行類加載工作
   } catch (IOException e) {
         throw new ClassNotFoundException(name, e);
   }
} else {
       return null;
}

回顧類加載器的委派模式,我們發(fā)現(xiàn),發(fā)起類加載請求的類加載器,不一定會最終執(zhí)行類的加載操作,可能其父類加載器來執(zhí)行類加載。

發(fā)起類加載請求的類加載器稱為初始類加載器;完成類加載操作的類加載器稱為定義類加載器。(為啥叫定義類加載?因名而得唄-defineClass)

有讀者老爺發(fā)問了,為什么要設(shè)計(jì)這種委派模式

委派模式的作用

個(gè)人理解,設(shè)計(jì)這種委派模式的用意有兩點(diǎn):
第一,避免對象的重復(fù)加載
第二,保護(hù)Java基礎(chǔ)類型的行為

關(guān)于第一點(diǎn),自然不必多說。
對于第二點(diǎn),咱們設(shè)想一下,在沒有委派模式的場景中,用戶自己編寫了一個(gè)java.lang.String類,并將其放置在程式的類路徑(ClassPath)中,那系統(tǒng)中就存在了多個(gè)不同的String類,這使得Java的基礎(chǔ)類型的行為無法得到保證,程式也會變得很混亂。

細(xì)心的讀者可能會從上面的例子中發(fā)現(xiàn),同一個(gè)類被兩個(gè)不同的類加載器加載會出現(xiàn)問題,產(chǎn)生這種問題的原因是什么?在后面的類加載器的隔離性中會進(jìn)行解釋

類加載器的層級結(jié)構(gòu)是怎樣的?

如下是類加載器層級示意圖:


口說無憑,咱們來驗(yàn)證一下類加載器的層級結(jié)構(gòu),執(zhí)行如下代碼:

 package com.hys;

 public class Main {

     public static void main(String[] args) {
 
         ClassLoader c1 = User.class.getClassLoader();
         System.out.println(c1);
 
          ClassLoader c2 = c1.getParent();
          System.out.println(c2);         

          ClassLoader c3 = c2.getParent(); 
          System.out.println(c3); 
    }
}

執(zhí)行結(jié)果:

sun.misc.Launcher$AppClassLoader@b4aac2
sun.misc.Launcher$ExtClassLoader@140e19d
null 

Process finished with exit code 0

執(zhí)行結(jié)果符合咱們的預(yù)期。

有讀者老爺發(fā)問了,怎么就符合預(yù)期了?結(jié)果中有個(gè)值為null,是個(gè)啥?Bootstrap ClassLoader呢?
原因在于頂級類加載器是Bootstrap ClassLoader,此加載器是C++代碼編寫,所以,使用Java獲取的時(shí)候,返回了null

類加載器的隔離性

JVM如何判斷一個(gè)類的唯一性?
JVM通過加載該類的類加載器和類名稱來確定一個(gè)類的唯一性。

這意味著我們使用兩個(gè)不同的類加載器加載同一個(gè)類,而在JVM層面上卻認(rèn)為這是兩個(gè)完全不同的類,從另一個(gè)層面看,相當(dāng)于類被類加載器所包裹與另個(gè)類加載器中的類進(jìn)行了隔離

如何證明這種隔離的存在?執(zhí)行如下代碼:

package com.hys;

public class Main {
    public static void main(String[] args) {

        try{
           User u1 = new User();
           User u2 = new User();
           // u1和u2都是同一個(gè)類加載器加載的
           System.out.println("The same classloader: u1.isInstance(u2)" + u1.getClass().isInstance(u2));

            MyClassLoader c1 = new MyClassLoader();
            Class clazz = c1.loadClass("com.hys.User");
           // clazz是MyClassLoader類加載器加載的
            System.out.println("The different classloader: clazz.isInstance(u1)" + clazz.isInstance(u1));
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
}

執(zhí)行結(jié)果:

The same classloader: u1.isInstance(u2)true
The different classloader: clazz.isInstance(u1)false

Process finished with exit code 0

自定義類加載器

自定義類加載器,一般是繼承ClassLoader類,覆蓋findClass(String name)方法,在此方法中做查詢與讀取.class文件的操作,如下代碼:

package com.hys;

import java.io.*;

public class MyClassLoader extends ClassLoader {

    private String mRootDir;

    public MyClassLoader() {

    }

    public MyClassLoader(String rootDir) {
        mRootDir = rootDir;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = loadClassData(name);
        if (data == null) {
            throw new ClassNotFoundException();
        }else{
            return defineClass(name, data, 0, data.length);
        }
    }

    private byte[]loadClassData(String className) {
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String classNameToPath(String className) {

        String classPath;
        if(mRootDir == null || mRootDir.isEmpty())
            classPath = className.replace('.', File.separatorChar) + ".class";
        else
            classPath = mRootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";

        return classPath;
    }
}

我是青嵐之峰,如果讀完后覺的有所收獲,歡迎點(diǎn)贊加關(guān)注

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,510評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內(nèi)容