深入解析Java中的Classloader的運(yùn)行機(jī)制
java有兩種類(lèi)型的classload,一種是user-defined的,一種是jvm內(nèi)置的bootstrap class loader,所有user-defined的class loader都是java.lang.ClassLoader的子類(lèi).
而jvm內(nèi)置的class loader有3種,分別是 Bootstrap ClassLoader, Extension ClassLoader(即ExtClassLoader),System ClassLoader(即AppClassLoader).
而jvm加載時(shí)的雙親委派 就不說(shuō)了,javaeye上有很多文章都有介紹..
可以分別看一下他們的構(gòu)造器,其中Bootstrap ClassLoader是用c寫(xiě)的.
java.lang.ClassLoader
protected ClassLoader(ClassLoader parent) { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkCreateClassLoader(); } //給父loader賦值. this.parent = parent; initialized = true; } protected ClassLoader() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkCreateClassLoader(); } //這邊將會(huì)把AppClassLoader付給父loader this.parent = getSystemClassLoader(); initialized = true; }
這個(gè)構(gòu)造器有帶參數(shù)的,和不帶構(gòu)造器的2個(gè)。帶參數(shù)的構(gòu)造器傳入的是這個(gè) class loader的父loader,而不帶參數(shù)的構(gòu)造器則會(huì)把getSystemClassLoader()所返回的class loader當(dāng)作他自己的父裝載器.下面我們來(lái)看getSystemClassLoader()的代碼
public static ClassLoader getSystemClassLoader() { //所返回的class loader在這個(gè)方法里面被賦值 initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader ccl = getCallerClassLoader(); if (ccl != null && ccl != scl && !scl.isAncestor(ccl)) { sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); } } return scl; } private static synchronized void initSystemClassLoader() { if (!sclSet) { if (scl != null) throw new IllegalStateException("recursive invocation"); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l != null) { Throwable oops = null; //在這邊被賦值 scl = l.getClassLoader(); ................................... ....................................... } } sclSet = true; } }
這邊的父class loader就是scl,也就是l.getClassLoader()所得到的,getClassLoader(),接下來(lái)看Launcher的源碼:
private static Launcher launcher = new Launcher();
public static Launcher getLauncher() { return launcher; } private ClassLoader loader; public Launcher() { // Create the extension class loader ClassLoader extcl; try { //這里傳入構(gòu)造器的parent為空 extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader"); } // Now create the class loader to use to launch the application try { //這邊可以看到默認(rèn)的loader就是AppClassLoader,也就是說(shuō)getSystemClassLoader返回的就是AppClassLoader loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader"); } //每一個(gè)當(dāng)前線程一個(gè)classload,以防止多線程中的classload引起的混亂(這個(gè)是我自己理解的,呵呵) // Also set the context class loader for the primordial thread. Thread.currentThread().setContextClassLoader(loader); ................................... ................................................ } /* * Returns the class loader used to launch the main application. */ public ClassLoader getClassLoader() { return loader; }
從中我們看到AppClassLoader的父loader是ExtClassLoader,而ExtClassLoader的父loader是什么呢?我們?cè)趤?lái)看ExtClassLoader的構(gòu)造器:
public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); this.dirs = dirs; }
他的父loader為空,而他的頂級(jí)父類(lèi)是java.lang.ClassLoader而當(dāng)傳入的parent為null時(shí),我們使用 ExtClassLoader load一個(gè)類(lèi)時(shí),系統(tǒng)會(huì)調(diào)用Bootstrap ClassLoader.
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { //先調(diào)用父loader來(lái)load. c = parent.loadClass(name, false); } else { //調(diào)用Bootstrap ClassLoader來(lái)load c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
而這里findBootstrapClass0也就是調(diào)用Bootstrap ClassLoader這個(gè)最核心的class loader來(lái)load class.
最終我們可以看到getSystemClassLoader() 返回的class loader就是AppClassLoader.
Java Classloader機(jī)制解析
JDK默認(rèn)ClassLoader
JDK 默認(rèn)提供了如下幾種ClassLoader
Bootstrp loader
Bootstrp加載器是用C++語(yǔ)言寫(xiě)的,它是在Java虛擬機(jī)啟動(dòng)后初始化的,它主要負(fù)責(zé)加載%JAVA_HOME%/jre/lib,-Xbootclasspath參數(shù)指定的路徑以及%JAVA_HOME%/jre/classes中的類(lèi)。
ExtClassLoader
Bootstrp loader加載ExtClassLoader,并且將ExtClassLoader的父加載器設(shè)置為Bootstrp loader.ExtClassLoader是用Java寫(xiě)的,具體來(lái)說(shuō)就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加載%JAVA_HOME%/jre/lib/ext,此路徑下的所有classes目錄以及java.ext.dirs系統(tǒng)變量指定的路徑中類(lèi)庫(kù)。
AppClassLoader
Bootstrp loader加載完ExtClassLoader后,就會(huì)加載AppClassLoader,并且將AppClassLoader的父加載器指定為 ExtClassLoader。AppClassLoader也是用Java寫(xiě)成的,它的實(shí)現(xiàn)類(lèi)是 sun.misc.Launcher$AppClassLoader,另外我們知道ClassLoader中有個(gè)getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要負(fù)責(zé)加載classpath所指定的位置的類(lèi)或者是jar文檔,它也是Java程序默認(rèn)的類(lèi)加載器。
綜上所述,它們之間的關(guān)系可以通過(guò)下圖形象的描述:
雙親委托模型
Java中ClassLoader的加載采用了雙親委托機(jī)制,采用雙親委托機(jī)制加載類(lèi)的時(shí)候采用如下的幾個(gè)步驟:
當(dāng)前ClassLoader首先從自己已經(jīng)加載的類(lèi)中查詢(xún)是否此類(lèi)已經(jīng)加載,如果已經(jīng)加載則直接返回原來(lái)已經(jīng)加載的類(lèi)。
每個(gè)類(lèi)加載器都有自己的加載緩存,當(dāng)一個(gè)類(lèi)被加載了以后就會(huì)放入緩存,等下次加載的時(shí)候就可以直接返回了。
當(dāng)前classLoader的緩存中沒(méi)有找到被加載的類(lèi)的時(shí)候,委托父類(lèi)加載器去加載,父類(lèi)加載器采用同樣的策略,首先查看自己的緩存,然后委托父類(lèi)的父類(lèi)去加載,一直到bootstrp ClassLoader.
當(dāng)所有的父類(lèi)加載器都沒(méi)有加載的時(shí)候,再由當(dāng)前的類(lèi)加載器加載,并將其放入它自己的緩存中,以便下次有加載請(qǐng)求的時(shí)候直接返回。
說(shuō)到這里大家可能會(huì)想,Java為什么要采用這樣的委托機(jī)制?理解這個(gè)問(wèn)題,我們引入另外一個(gè)關(guān)于Classloader的概念“命名空間”, 它是指要確定某一個(gè)類(lèi),需要類(lèi)的全限定名以及加載此類(lèi)的ClassLoader來(lái)共同確定。也就是說(shuō)即使兩個(gè)類(lèi)的全限定名是相同的,但是因?yàn)椴煌?ClassLoader加載了此類(lèi),那么在JVM中它是不同的類(lèi)。明白了命名空間以后,我們?cè)賮?lái)看看委托模型。采用了委托模型以后加大了不同的 ClassLoader的交互能力,比如上面說(shuō)的,我們JDK本生提供的類(lèi)庫(kù),比如hashmap,linkedlist等等,這些類(lèi)由bootstrp 類(lèi)加載器加載了以后,無(wú)論你程序中有多少個(gè)類(lèi)加載器,那么這些類(lèi)其實(shí)都是可以共享的,這樣就避免了不同的類(lèi)加載器加載了同樣名字的不同類(lèi)以后造成混亂。
如何自定義ClassLoader
Java除了上面所說(shuō)的默認(rèn)提供的classloader以外,它還容許應(yīng)用程序可以自定義classloader,那么要想自定義classloader我們需要通過(guò)繼承java.lang.ClassLoader來(lái)實(shí)現(xiàn),接下來(lái)我們就來(lái)看看再自定義Classloader的時(shí)候,我們需要注意的幾個(gè)重要的方法:
1.loadClass 方法
loadClass method declare
public Class<?> loadClass(String name) throws ClassNotFoundException
上面是loadClass方法的原型聲明,上面所說(shuō)的雙親委托機(jī)制的實(shí)現(xiàn)其實(shí)就實(shí)在此方法中實(shí)現(xiàn)的。下面我們就來(lái)看看此方法的代碼來(lái)看看它到底如何實(shí)現(xiàn)雙親委托的。
loadClass method implement
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); }
從上面可以看出loadClass方法調(diào)用了loadcClass(name,false)方法,那么接下來(lái)我們?cè)賮?lái)看看另外一個(gè)loadClass方法的實(shí)現(xiàn)。
Class loadClass(String name, boolean resolve)
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); //檢查class是否已經(jīng)被加載過(guò)了 if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); //如果沒(méi)有被加載,且指定了父類(lèi)加載器,則委托父加載器加載。 } else { c = findBootstrapClass0(name);//如果沒(méi)有父類(lèi)加載器,則委托bootstrap加載器加載 } } catch (ClassNotFoundException e) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name);//如果父類(lèi)加載沒(méi)有加載到,則通過(guò)自己的findClass來(lái)加載。 } } if (resolve) { resolveClass(c); } return c; }
上面的代碼,我加了注釋通過(guò)注釋可以清晰看出loadClass的雙親委托機(jī)制是如何工作的。 這里我們需要注意一點(diǎn)就是public Class<?> loadClass(String name) throws ClassNotFoundException沒(méi)有被標(biāo)記為final,也就意味著我們是可以override這個(gè)方法的,也就是說(shuō)雙親委托機(jī)制是可以打破的。另外上面注意到有個(gè)findClass方法,接下來(lái)我們就來(lái)說(shuō)說(shuō)這個(gè)方法到底是搞末子的。
2.findClass
我們查看java.lang.ClassLoader的源代碼,我們發(fā)現(xiàn)findClass的實(shí)現(xiàn)如下:
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
我們可以看出此方法默認(rèn)的實(shí)現(xiàn)是直接拋出異常,其實(shí)這個(gè)方法就是留給我們應(yīng)用程序來(lái)override的。那么具體的實(shí)現(xiàn)就看你的實(shí)現(xiàn)邏輯了,你可以從磁盤(pán)讀取,也可以從網(wǎng)絡(luò)上獲取class文件的字節(jié)流,獲取class二進(jìn)制了以后就可以交給defineClass來(lái)實(shí)現(xiàn)進(jìn)一步的加載。defineClass我們?cè)傧旅嬖賮?lái)描述。 ok,通過(guò)上面的分析,我們可以得出如下結(jié)論:
我們?cè)趯?xiě)自己的ClassLoader的時(shí)候,如果想遵循雙親委托機(jī)制,則只需要override findClass.
3.defineClass
我們首先還是來(lái)看看defineClass的源碼:
defineClass
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError { return defineClass(name, b, off, len, null); }
從上面的代碼我們看出此方法被定義為了final,這也就意味著此方法不能被Override,其實(shí)這也是jvm留給我們的唯一的入口,通過(guò)這個(gè)唯 一的入口,jvm保證了類(lèi)文件必須符合Java虛擬機(jī)規(guī)范規(guī)定的類(lèi)的定義。此方法最后會(huì)調(diào)用native的方法來(lái)實(shí)現(xiàn)真正的類(lèi)的加載工作。
Ok,通過(guò)上面的描述,我們來(lái)思考下面一個(gè)問(wèn)題:
假如我們自己寫(xiě)了一個(gè)java.lang.String的類(lèi),我們是否可以替換調(diào)JDK本身的類(lèi)?
答案是否定的。我們不能實(shí)現(xiàn)。為什么呢?我看很多網(wǎng)上解釋是說(shuō)雙親委托機(jī)制解決這個(gè)問(wèn)題,其實(shí)不是非常的準(zhǔn)確。因?yàn)殡p親委托機(jī)制是可以打破的,你完全可以自己寫(xiě)一個(gè)classLoader來(lái)加載自己寫(xiě)的java.lang.String類(lèi),但是你會(huì)發(fā)現(xiàn)也不會(huì)加載成功,具體就是因?yàn)獒槍?duì)java.*開(kāi)頭的類(lèi),jvm的實(shí)現(xiàn)中已經(jīng)保證了必須由bootstrp來(lái)加載。
不遵循“雙親委托機(jī)制”的場(chǎng)景
上面說(shuō)了雙親委托機(jī)制主要是為了實(shí)現(xiàn)不同的ClassLoader之間加載的類(lèi)的交互問(wèn)題,被大家公用的類(lèi)就交由父加載器去加載,但是Java中確實(shí)也存在父類(lèi)加載器加載的類(lèi)需要用到子加載器加載的類(lèi)的情況。下面我們就來(lái)說(shuō)說(shuō)這種情況的發(fā)生。
Java中有一個(gè)SPI(Service Provider Interface)標(biāo)準(zhǔn),使用了SPI的庫(kù),比如JDBC,JNDI等,我們都知道JDBC需要第三方提供的驅(qū)動(dòng)才可以,而驅(qū)動(dòng)的jar包是放在我們應(yīng) 用程序本身的classpath的,而jdbc 本身的api是jdk提供的一部分,它已經(jīng)被bootstrp加載了,那第三方廠商提供的實(shí)現(xiàn)類(lèi)怎么加載呢?這里面JAVA引入了線程上下文類(lèi)加載的概 念,線程類(lèi)加載器默認(rèn)會(huì)從父線程繼承,如果沒(méi)有指定的話(huà),默認(rèn)就是系統(tǒng)類(lèi)加載器(AppClassLoader),這樣的話(huà)當(dāng)加載第三方驅(qū)動(dòng)的時(shí)候,就可 以通過(guò)線程的上下文類(lèi)加載器來(lái)加載。
另外為了實(shí)現(xiàn)更靈活的類(lèi)加載器OSGI以及一些Java app server也打破了雙親委托機(jī)制。
相關(guān)文章
基于Java實(shí)現(xiàn)緩存Cache的深入分析
本篇文章是對(duì)Java實(shí)現(xiàn)緩存Cache進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06springboot如何獲取applicationContext?servletContext
這篇文章主要介紹了springboot如何獲取applicationContext?servletContext問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01springboot如何使用AOP做訪問(wèn)請(qǐng)求日志
這篇文章主要介紹了springboot如何使用AOP做訪問(wèn)請(qǐng)求日志,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01簡(jiǎn)單談?wù)刯ava的異常處理(Try Catch Finally)
在程序設(shè)計(jì)中,進(jìn)行異常處理是非常關(guān)鍵和重要的一部分。一個(gè)程序的異常處理框架的好壞直接影響到整個(gè)項(xiàng)目的代碼質(zhì)量以及后期維護(hù)成本和難度。2016-03-03MyBatis中動(dòng)態(tài)SQL語(yǔ)句@Provider的用法
本文主要介紹了MyBatis中動(dòng)態(tài)SQL語(yǔ)句@Provider的用法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06Java中解密微信加密數(shù)據(jù)工具類(lèi)
最近小編一直在開(kāi)發(fā)微信公眾號(hào)、小程序項(xiàng)目,微信返回給我們的數(shù)據(jù)都是加密的,我們需要使用sessionkey配合解密,才能看到我們想要的數(shù)據(jù),基于代碼怎么實(shí)現(xiàn)呢,下面小編給大家?guī)?lái)了Java中解密微信加密數(shù)據(jù)工具類(lèi)的完整代碼,一起看看吧2021-06-06Mybatis動(dòng)態(tài)拼接sql提高插入速度實(shí)例
這篇文章主要介紹了Mybatis動(dòng)態(tài)拼接sql提高插入速度實(shí)例,當(dāng)數(shù)據(jù)量少的時(shí)候,沒(méi)問(wèn)題,有效時(shí)間內(nèi)可能完成插入,但是當(dāng)數(shù)據(jù)量達(dá)到一定程度的時(shí)候,每次都一個(gè)sql插入超時(shí),所以采用了拼接sql的方式加快速度,需要的朋友可以參考下2023-09-09postgresql 實(shí)現(xiàn)16進(jìn)制字符串轉(zhuǎn)10進(jìn)制數(shù)字
這篇文章主要介紹了postgresql 實(shí)現(xiàn)16進(jìn)制字符串轉(zhuǎn)10進(jìn)制數(shù)字操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02Java中的Map接口實(shí)現(xiàn)類(lèi)HashMap和LinkedHashMap詳解
這篇文章主要介紹了Java中的Map接口實(shí)現(xiàn)類(lèi)HashMap和LinkedHashMap詳解,我們常會(huì)看到這樣的一種集合,IP地址與主機(jī)名,等,這種一一對(duì)應(yīng)的關(guān)系,就叫做映射,Java提供了專(zhuān)門(mén)的集合類(lèi)用來(lái)存放這種對(duì)象關(guān)系的對(duì)象,需要的朋友可以參考下2024-01-01