Java 類加載器

1). 字節(jié)碼和.class文件區(qū)別

新建Java對象時(shí)谷羞,JVM將這個對象對應(yīng)的字節(jié)碼加載到內(nèi)存中帝火,這個字節(jié)碼的原始信息存放在classpath目錄下(及Java工程的bin目錄下)的.class文件中,類加載需要將.class文件導(dǎo)入硬盤湃缎,經(jīng)過處理變成字節(jié)碼加載到內(nèi)存犀填。
示例:

public class ClassLoaderTest {
    public static void main(String[] args) {
        // 輸出ClassLoaderTest的類加載器名稱
        System.out.println("ClassLoaderTest類的類加載器名稱:" + ClassLoaderTest.class.getClassLoader().getClass().getName());
        System.out.println("System類的類加載器名稱:" + System.class.getClassLoader());
        System.out.println("List類的類加載器名稱:" + List.class.getClassLoader());
        
        // 獲取ClassLoaderTest類的類加載器
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        while (null != classLoader) {
            System.out.println(classLoader.getClass().getName() + "->");
            // 獲取類加載器的父類
            classLoader = classLoader.getParent();
        }
        System.out.println(classLoader);        
    }
}

打印結(jié)果:

圖1.png

從打印結(jié)果可以看出ClassLoaderTest類是有AppClassLoader加載的。
因?yàn)镾ystem類嗓违,List九巡,Map等這樣的系統(tǒng)提供jar類都在rt.jar中,所以由BootStrap類加載器加載蹂季,因?yàn)锽ootStrap是祖先類冕广,不是Java編寫的疏日,所以打印出class為null。詳情請看第三點(diǎn):類加載器的委托機(jī)制

2). Java虛擬中的類加載器

Java虛擬機(jī)中可以安裝多個類加載器撒汉,系統(tǒng)默認(rèn)的有三個沟优,每個類負(fù)責(zé)加載特定位置的類:BootStrapExtClassLoader睬辐,AppClassLoader
由于類加載器本身是Java類挠阁,也需要被類加載器加載,因此必須有一個為類加載器不是Java類溉委,因此Bootstrap是由C/C++代碼編寫鹃唯,被封裝到JVM內(nèi)核中,ExtClassLoader和AppClassLoader是Java類瓣喊。
類加載器屬性結(jié)構(gòu)圖:

圖2.png

3). 類加載器的委托機(jī)制

問題:Java虛擬機(jī)要加載第一個類的時(shí)候坡慌,使用哪一個類加載器加載?
(1). 當(dāng)前線程的類加載器加載線程中的第一個類(當(dāng)前線程的類加載器:Thread類中有一個get/setContextClassLoader(ClassLoader);方法藻三,可以獲取/指定本線程中的類加載器)
(2). 如果類A引用了類B洪橘,Java虛擬機(jī)將使用加載類A的加載器來加載類B
(3). 可以直接調(diào)用ClassLoader.loadClass(String className)方法來指定某個類加載器去加載某個類

每個類加載器加載類時(shí),先委托其上級類加載器來加載棵帽,當(dāng)所有父加載器沒有加載到類時(shí)熄求,回到發(fā)起者類加載器,如果還是加載不了逗概,則會拋出ClassNotFoundException弟晚。

好處:
能夠很好的統(tǒng)一管理類加載,首先交給上級逾苫,如果上級有卿城,就加載,這樣如果之前已經(jīng)加載過的類铅搓,這時(shí)候在來加載它的時(shí)候只要拿過來用就可以了瑟押,無需二次加載

4). 測試ExtClassLoader
  • 將ClassLoaderTest類打包為Jar文件,拷貝至jdk\jre\lib\ext\下路徑
    圖3.png
  • 接下來在eclipse中運(yùn)行ClassLoaderTest文件星掰,其打印結(jié)果為
    圖4.png

    打印結(jié)果驗(yàn)證了類加載器的委托機(jī)制.
    測試完成后記得將test.jar刪除
5). 類加載器主要方法
  • 默認(rèn)的類加載器
System.out.println("默認(rèn)的類加載器:"+ClassLoader.getSystemClassLoader());  
圖5.png
  • loadClass(String name) 源碼
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        //  加鎖多望,進(jìn)行同步處理,可能多個線程加載類
        synchronized (getClassLoadingLock(name)) {
            // 檢查是否已經(jīng)加載過
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 如果自定義的類加載器的parent不為null,就調(diào)用parent的loadClass進(jìn)行類加載
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 如果自定義的類加載器的parent為null氢烘,就調(diào)用findBootstrapClassOrNull方法查找類怀偷,就是BootStrap類加載器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 類未發(fā)現(xiàn)異常
                }

                if (c == null) {
                    // 如果仍然沒有發(fā)現(xiàn),就調(diào)用自己的findClass方法進(jìn)行進(jìn)行類加載
                    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;
        }
    }
  • defineClass播玖,將class文件的字節(jié)數(shù)組編譯成一個class對象椎工,不能重寫,內(nèi)部實(shí)現(xiàn)為C/C++代碼


    圖6.png
6). 自定義ClassLoader
  • 定義一個被加載的類
/**
 * 加載類
 * @author mazaiting
 */
public class ClassLoaderAttachment extends Date{
    private static final long serialVersionUID = -1892974244318329380L;
    
    @Override
    public String toString() {
        return "Hello ClassLoader";
    }
}
  • 自定義ClassLoader
/**
 * 自定義類加載器
 * 步驟:
 *  1. 繼承ClasssLoader
 *  2. 重寫findClass(String name) 方法
 * @author mazaiting
 */
public class MyClassLoader extends ClassLoader{
    /**需要加載類.class文件的目錄*/
    private String classDir;
    /**無參構(gòu)造*/
    public MyClassLoader() {}
    public MyClassLoader(String classDir) {
        this.classDir = classDir;
    }
    
    // 測試,先將ClassLoaderAttachment.class文件加密到工程的class_temp目錄下
    public static void main(String[] args) throws Exception {
        // 配置運(yùn)行參數(shù)
        // ClassLoaderAttachment.class原路徑--E:\test\java_advanced\ClassLoader\bin\com\mazaiting\ClassLoaderAttachment.class 
        String srcPath = args[0];
        // ClassLoaderAttachment.class輸出路徑--E:\test\java_advanced\ClassLoader\class_temp\
        String desPath = args[1];
        // 獲取文件名
        String desFileName = srcPath.substring(srcPath.lastIndexOf("\\") +1);
        // 配置目標(biāo)文件路徑
        String desPathFile = desPath + "/" + desFileName;
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(desPathFile);
        // 將class進(jìn)行加密
        encodeAndDecode(fis, fos);
        fis.close();
        fos.close();
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // class文件的路徑
        String classPathFile = classDir + "/" + name + ".class";
        try {
            // 將class文件進(jìn)行解密
            FileInputStream fis = new FileInputStream(classPathFile);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            encodeAndDecode(fis, baos);
            byte[] classByte = baos.toByteArray();
            // 將字節(jié)流變成一個class
            return defineClass(null, classByte, 0, classByte.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return super.findClass(name);
    }
    
    /**
     * 加密解密
     * @param is 輸入流
     * @param os 輸出流
     * @throws IOException 
     */
    private static void encodeAndDecode(InputStream is, OutputStream os) throws IOException {
        int len;
        while ((len = is.read()) != -1) {
            len ^= 0xFF;
            os.write(len);
        }
    }
}
  • 運(yùn)行:在MyClassLoader的main方法右鍵 -> Run As -> Run Configurations...晋渺,進(jìn)行如下配置(配置前請確保ClassLoader工程目錄下存在class_temp文件夾,其中第一個參數(shù)是ClassLoaderAttachment.class文件的源路徑脓斩,第二個參數(shù)是加密后存放的目錄)木西,點(diǎn)擊Run運(yùn)行,此時(shí)在E:\test\java_advanced\ClassLoader\class_temp將會生成加密后的ClassLoaderAttachment.class文件
    圖7.png
  • 使用随静,在ClassLoaderTest的main函數(shù)中應(yīng)用
public class ClassLoaderTest {
    public static void main(String[] args) {
        try {
            // 加載類
            Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");
            // 創(chuàng)建實(shí)例
            Date date = (Date) classDate.newInstance();
            // 輸出ClassLoaderAttachment類的加載器名稱
            System.out.println("ClassLoader: " + date.getClass().getClassLoader().getClass().getName());
            // 調(diào)用方法
            System.out.println(date.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打印結(jié)果:


圖8.png
  • 如果loadClass方法的參數(shù)為類的全路徑名
    // 加載類
    Class classDate = new MyClassLoader("class_temp").loadClass("com.mazaiting.ClassLoaderAttachment");
圖9.png
  • \class_temp\路徑下的ClassLoaderAttachment.class復(fù)制到\bin\com\mazaiting\路徑下八千,然后運(yùn)行
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 889275713 in class file com/mazaiting/ClassLoaderAttachment

圖10.png

不合適的魔數(shù)錯誤(class文件的前六個字節(jié)是個魔數(shù)用來標(biāo)識class文件的),因?yàn)榧用芎蟮臄?shù)據(jù)AppClasssLoader無法解析燎猛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恋捆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子重绷,更是在濱河造成了極大的恐慌沸停,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昭卓,死亡現(xiàn)場離奇詭異愤钾,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)候醒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門能颁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人倒淫,你說我怎么就攤上這事伙菊。” “怎么了敌土?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵镜硕,是天一觀的道長。 經(jīng)常有香客問我纯赎,道長谦疾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任犬金,我火速辦了婚禮念恍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晚顷。我一直安慰自己峰伙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布该默。 她就那樣靜靜地躺著瞳氓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栓袖。 梳的紋絲不亂的頭發(fā)上匣摘,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天店诗,我揣著相機(jī)與錄音,去河邊找鬼音榜。 笑死庞瘸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赠叼。 我是一名探鬼主播擦囊,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嘴办!你這毒婦竟也來了瞬场?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤涧郊,失蹤者是張志新(化名)和其女友劉穎贯被,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妆艘,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刃榨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了双仍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枢希。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖朱沃,靈堂內(nèi)的尸體忽然破棺而出苞轿,到底是詐尸還是另有隱情,我是刑警寧澤逗物,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布搬卒,位于F島的核電站,受9級特大地震影響翎卓,放射性物質(zhì)發(fā)生泄漏契邀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一失暴、第九天 我趴在偏房一處隱蔽的房頂上張望坯门。 院中可真熱鬧,春花似錦逗扒、人聲如沸古戴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽现恼。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叉袍,已是汗流浹背始锚。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喳逛,地道東北人疼蛾。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像艺配,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子衍慎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

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

  • 今晚8點(diǎn)半要講微課转唉,但是又聽說某人說某個電影好看,所以就約了老公要看電影稳捆。結(jié)果一邊看電影一邊轉(zhuǎn)播微課赠法。紅色好奇心起...
    黃玉翠閱讀 110評論 0 0
  • 當(dāng)列車緩緩啟動砖织,看著窗外熟悉的站牌向后逃離視線之外,我才緩過神來:原來這就要返程了末荐。站臺上自然少不了送別的人侧纯,只是...
    高小閑閱讀 502評論 3 3
  • 魔力功課第二天: 1. 寫下10個讓你感恩的事項(xiàng),以及原因甲脏。句子格式類似: 我感謝xx, 因?yàn)?xx , 或是 我...
    上官禎莜閱讀 145評論 0 0
  • 節(jié)奏感是學(xué)鋼琴的基礎(chǔ) 節(jié)奏感是學(xué)鋼琴的基礎(chǔ).拍感準(zhǔn)块请,也就是能準(zhǔn)確掌握每拍的時(shí)值娜氏。任何人都有天生的節(jié)拍感 任何人都有...
    af88c59abbf6閱讀 933評論 0 1
  • 風(fēng)雨一爐,滿地江湖墩新。有人說世間的雨是為文青下的贸弥,如果你還在尋覓青春的最后一句,如果你還在想來路的那個腳印海渊,如果你還...
    文青茶館閱讀 278評論 2 1