Java虛擬機(jī)--類加載器源碼

類加載器源碼分析

下面,我們就來(lái)深入的學(xué)習(xí)下類加載器的源碼筷屡,看看到底做了哪些事情涧偷?

類加載體系

上圖呈現(xiàn)是源碼級(jí)別的類加載體系,ClassLoader是基類速蕊,所有的類加載器都需要繼承它(啟動(dòng)類加載器除外)嫂丙。

首先,我們通過(guò)上文中的測(cè)試類來(lái)舉例规哲,一點(diǎn)點(diǎn)剖析類加載的流程跟啤。

創(chuàng)建一個(gè)包下普通的類:com.jiaboyan.test.ObjectTest

public class ObjectTest {
}

將此類編譯,并打包處理:

jar cvf ObjectTest.jar com\jiaboyan\test\ObjectTest.class

將此jar包放入<Java_Runtime_Home>/lib/ext目錄下唉锌;

編寫(xiě)測(cè)試用例:

public class JVMTest5 {
   public static void main(String[] agrs) throws ClassNotFoundException {
       Class clazz = Class.forName("com.jiaboyan.test.ObjectTest");
       System.out.println(clazz.getClassLoader());
   }
}

使用Class.forName來(lái)加載com.jiaboyan.test.ObjectTest類隅肥,看內(nèi)部具體流程。

類加載流程

(1)進(jìn)入到Class內(nèi)部袄简,可以看到實(shí)際調(diào)用了native修飾的forName0()方法腥放。

public static Class<?> forName(String className) throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

private static native Class<?> forName0(String name, boolean initialize,
                                        ClassLoader loader,
                                        Class<?> caller) throws ClassNotFoundException;

(2)通過(guò)debug來(lái)看,在forName0()后又調(diào)用了AppClassLoader類的loadClass(String var1, boolean var2)方法绿语。

public Class loadClass(String var1, boolean var2) throws ClassNotFoundException {
    int var3 = var1.lastIndexOf(46);
    if (var3 != -1) {
        SecurityManager var4 = System.getSecurityManager();
        if (var4 != null) {
            var4.checkPackageAccess(var1.substring(0, var3));
        }
    }
    return super.loadClass(var1, var2);
}

在方法的末尾秃症,調(diào)用父類中的loadClass(String name, boolean resolve)方法,也就是ClassLoader類吕粹;

(3)通過(guò)debug來(lái)看种柑,在forName0()后又調(diào)用了ClassLoader類的loadClass(String name, boolean resolve)方法。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
//先從緩存查找該class對(duì)象匹耕,找到就不用重新加載
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果找不到聚请,則委托給父類加載器去加載
c = parent.loadClass(name, false);
} else {
//如果沒(méi)有父類,則委托給啟動(dòng)加載器去加載
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//拋出異常無(wú)需理會(huì)
}

            if (c == null) {
                long t1 = System.nanoTime();
                //如果都沒(méi)有找到稳其,則通過(guò)findClass去查找并加載
                c = findClass(name);
                .....
            }
        }
        //是否需要在加載時(shí)進(jìn)行解析
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

loadClass源碼所示:當(dāng)類加載請(qǐng)求到來(lái)時(shí)驶赏,先從緩存中查找該類對(duì)象,如果存在則直接返回既鞠,如果不存在則交給該類加載器的父類加載器去加載煤傍,倘若沒(méi)有父類加載器則交給頂級(jí)啟動(dòng)類加載器去加載,最后仍沒(méi)有找到嘱蛋,則使用findClass()方法去加載蚯姆。

實(shí)際流程是椅寺,類加載請(qǐng)求進(jìn)來(lái)時(shí),this為AppClassLoader對(duì)象蒋失,判斷AppClassLoader對(duì)象的parent父類加載器是否為空。根據(jù)雙親委派模型得知桐玻,此時(shí)的parent一定為ExtClassLoader對(duì)象篙挽。調(diào)用ExtClassLoader的loadClass(String name, boolean resolve)方法。

通過(guò)源碼得知镊靴,ExtClassLoader繼承ClassLoader抽象類铣卡,并且沒(méi)有重寫(xiě)loadClass(String name, boolean resolve)方法。那么偏竟,此時(shí)又進(jìn)入到了上面的loadClass(String name, boolean resolve)中煮落。不過(guò),此時(shí)的this是ExtClassLoader對(duì)象踊谋。

ExtClassLoader對(duì)象的parent父類加載器為null蝉仇,調(diào)用findBootstrapClassOrNull(String name)方法,使用頂層啟動(dòng)類加載器去加載com.jiaboyan.test.ObjectTest類殖蚕。

由于頂層啟動(dòng)類加載器是C++實(shí)現(xiàn)轿衔,我們無(wú)法直接獲取到其引用,最終調(diào)用到了native修飾的findBootstrapClass(String name)方法睦疫。

由于害驹,我們將ObjectTest.jar放在了<Java_Runtime_Home>/lib/ext目錄下,所以頂層啟動(dòng)類加載器加載不到com.jiaboyan.test.ObjectTest類蛤育,繼而拋出異常宛官,返回到ExtClassLoader對(duì)象的loadClass(String name, boolean resolve)方法中來(lái)。

(4)接下來(lái)瓦糕,繼續(xù)調(diào)用findClass(name)方法底洗。

protected Class<?> findClass(final String name) throws ClassNotFoundException{
    try {
        return AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class>() {
                public Class run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            //生成class對(duì)象
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        throw new ClassNotFoundException(name);
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
}

由于現(xiàn)在的this為ExtClassLoader對(duì)象,所以我們調(diào)用ExtClassLoader對(duì)象的findClass(final String name)方法刻坊。通過(guò)查看源碼發(fā)現(xiàn)枷恕,ExtClassLoader并沒(méi)有findClass方法,不過(guò)再其父類URLClassLoader中有實(shí)現(xiàn)(代碼如上)谭胚。

(5)調(diào)用defineClass(String name, Resource res)方法徐块。

private Class defineClass(String name, Resource res) throws IOException {
    long t0 = System.nanoTime();
    int i = name.lastIndexOf('.');
    URL url = res.getCodeSourceURL();
    if (i != -1) {
        String pkgname = name.substring(0, i);
        Manifest man = res.getManifest();
        if (getAndVerifyPackage(pkgname, man, url) == null) {
            try {
                if (man != null) {
                    definePackage(pkgname, man, url);
                } else {
                    definePackage(pkgname, null, null, null, null, null, null, null);
                }
            } catch (IllegalArgumentException iae) {
                if (getAndVerifyPackage(pkgname, man, url) == null) {
                    throw new AssertionError("Cannot find package " + pkgname);
                }
            }
        }
    }
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        //獲取資源內(nèi)的字節(jié)流數(shù)組:
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, bb, cs);
    } else {
        //獲取資源內(nèi)的字節(jié)流數(shù)組:
        byte[] b = res.getBytes();
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, b, 0, b.length, cs);
    }
}

defineClass(String name, Resource res)方法是用來(lái)將byte字節(jié)流解析成JVM能夠識(shí)別的Class對(duì)象,通過(guò)這個(gè)方法不僅能夠通過(guò)class文件實(shí)例化class對(duì)象灾而,也可以通過(guò)其他方式實(shí)例化class對(duì)象胡控,如通過(guò)網(wǎng)絡(luò)接收一個(gè)類的字節(jié)碼,然后轉(zhuǎn)換為byte字節(jié)流創(chuàng)建對(duì)應(yīng)的Class對(duì)象旁趟。

最后調(diào)用了defineClass(String name, java.nio.ByteBuffer b,CodeSource cs)方法昼激,具體內(nèi)部細(xì)節(jié)筆者不在詳細(xì)陳序,需要說(shuō)明的是:Class對(duì)象依舊使用了native修飾的方法來(lái)完成,內(nèi)部細(xì)節(jié)無(wú)從得知橙困。

private native Class defineClass2(String name, java.nio.ByteBuffer b,
                                  int off, int len, ProtectionDomain pd,
                                  String source);

(6)至此ExtClassLoader對(duì)象的加載階段就此結(jié)束瞧掺,Class對(duì)象返回。當(dāng)返回到AppClassLoader對(duì)象中時(shí)凡傅,c并不為null辟狈,所以無(wú)需再次調(diào)用c = findClass(name)方法,整個(gè)類加載完成夏跷。

通過(guò)上述源碼可知哼转,當(dāng)我們自己定義一個(gè)類加載器時(shí)候,無(wú)需重寫(xiě)loadClass()方法槽华,直接重寫(xiě)自定義的findClass(String name)即可壹蔓。父類加載器加載失敗時(shí)候,最終都會(huì)走到findClass(String name)中來(lái)猫态。

說(shuō)完了類加載主體流程佣蓉,接下來(lái)我們來(lái)研究一點(diǎn)細(xì)節(jié)的東西!G籽F荨!

此時(shí)匆光,將文章拉回上面源碼體系截圖中套像,我們來(lái)看看SecureClassLoader、URLClassLoader類起到了哪些作用终息。

SercureClassLoader

SercureClassLoader繼承ClassLoader夺巩,擴(kuò)展了ClassLoader類的功能,增加對(duì)代碼源和權(quán)限定義類的驗(yàn)證周崭。

URLClassLoader

URLClassLoader繼承SercureClassLoader柳譬,實(shí)現(xiàn)了ClassLoader中定義的方法,例如:findClass()续镇、findResource()等美澳。

在URLClassLoader中有一個(gè)成員變量ucp--URLClassPath對(duì)象,URLClassPath的功能是通過(guò)傳入的路徑信息獲取要加載的字節(jié)碼摸航,字節(jié)碼可以是在.class文件中制跟、可以是在.jar包中,也可以是在網(wǎng)絡(luò)中酱虎。

public URLClassLoader(URL[] urls) {
    super();
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkCreateClassLoader();
    }
    ucp = new URLClassPath(urls);
    this.acc = AccessController.getContext();
}

在URLClassPath構(gòu)造中雨膨,需要傳入U(xiǎn)RL[]數(shù)組,通過(guò)這個(gè)URL[]數(shù)組中所指定的位置信息读串,去加載對(duì)應(yīng)的文件聊记。在URLClassPath內(nèi)部會(huì)根據(jù)傳遞的路徑是文件地址撒妈、jar包地址還是網(wǎng)絡(luò)地址來(lái)進(jìn)行判斷,來(lái)生成對(duì)應(yīng)Loader排监。

public URLClassPath(URL[] var1, URLStreamHandlerFactory var2) {
    this.path = new ArrayList();
    this.urls = new Stack();
    this.loaders = new ArrayList();
    this.lmap = new HashMap();
    this.closed = false;

    for(int var3 = 0; var3 < var1.length; ++var3) {
        this.path.add(var1[var3]);
    }

    this.push(var1);
    if (var2 != null) {
        this.jarHandler = var2.createURLStreamHandler("jar");
    }

}

根據(jù)路徑的不同狰右,來(lái)生成不同的Loader:

private URLClassPath.Loader getLoader(final URL var1) throws IOException {
    try {
        return (URLClassPath.Loader)AccessController.doPrivileged(new PrivilegedExceptionAction<URLClassPath.Loader>() {
            public URLClassPath.Loader run() throws IOException {
                String var1x = var1.getFile();
                if (var1x != null && var1x.endsWith("/")) {
                    return (URLClassPath.Loader)("file".equals(var1.getProtocol()) ? new URLClassPath.FileLoader(var1) : new URLClassPath.Loader(var1));
                } else {
                    return new URLClassPath.JarLoader(var1, URLClassPath.this.jarHandler, URLClassPath.this.lmap);
                }
            }
        });
    } catch (PrivilegedActionException var3) {
        throw (IOException)var3.getException();
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市舆床,隨后出現(xiàn)的幾起案子挟阻,更是在濱河造成了極大的恐慌,老刑警劉巖峭弟,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脱拼,居然都是意外死亡瞒瘸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)熄浓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)情臭,“玉大人,你說(shuō)我怎么就攤上這事赌蔑「┰冢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵娃惯,是天一觀的道長(zhǎng)跷乐。 經(jīng)常有香客問(wèn)我,道長(zhǎng)趾浅,這世上最難降的妖魔是什么愕提? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮皿哨,結(jié)果婚禮上浅侨,老公的妹妹穿的比我還像新娘。我一直安慰自己证膨,他們只是感情好如输,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著央勒,像睡著了一般不见。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上崔步,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天脖祈,我揣著相機(jī)與錄音,去河邊找鬼刷晋。 笑死盖高,一個(gè)胖子當(dāng)著我的面吹牛慎陵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喻奥,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼席纽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了撞蚕?” 一聲冷哼從身側(cè)響起润梯,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎甥厦,沒(méi)想到半個(gè)月后纺铭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刀疙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年舶赔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谦秧。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竟纳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疚鲤,到底是詐尸還是另有隱情锥累,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布集歇,位于F島的核電站桶略,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏诲宇。R本人自食惡果不足惜删性,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望焕窝。 院中可真熱鬧蹬挺,春花似錦、人聲如沸它掂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)虐秋。三九已至榕茧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間客给,已是汗流浹背用押。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留靶剑,地道東北人蜻拨。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓池充,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親缎讼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子收夸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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