深度分析 Java 的 ClassLoader 机制(源码级别)(转)
寫(xiě)在前面:Java中的所有類(lèi),必須被裝載到j(luò)vm中才能運(yùn)行,這個(gè)裝載工作是由jvm中的類(lèi)裝載器完成的,類(lèi)裝載器所做的工作實(shí)質(zhì)是把類(lèi)文件從硬盤(pán)讀取到內(nèi)存中,JVM在加載類(lèi)的時(shí)候,都是通過(guò)ClassLoader的loadClass()方法來(lái)加載class的,loadClass使用雙親委派模式。
為了更好的理解類(lèi)的加載機(jī)制,我們來(lái)深入研究一下ClassLoader和他的loadClass()方法。
源碼分析
| 1 | public abstract class ClassLoader |
ClassLoader類(lèi)是一個(gè)抽象類(lèi),sun公司是這么解釋這個(gè)類(lèi)的:
| 1 2 3 4 5 6 7 | /** ?* A class loader is an object that is responsible for loading classes. The ?* class ClassLoader is an abstract class.? Given the binary name of a class, a class loader should attempt to ?* locate or generate data that constitutes a definition for the class.? A ?* typical strategy is to transform the name into a file name and then read a ?* "class file" of that name from a file system. **/ |
大致意思如下:
class loader是一個(gè)負(fù)責(zé)加載classes的對(duì)象,ClassLoader類(lèi)是一個(gè)抽象類(lèi),需要給出類(lèi)的二進(jìn)制名稱(chēng),class loader嘗試定位或者產(chǎn)生一個(gè)class的數(shù)據(jù),一個(gè)典型的策略是把二進(jìn)制名字轉(zhuǎn)換成文件名然后到文件系統(tǒng)中找到該文件。
接下來(lái)我們看loadClass方法的實(shí)現(xiàn)方式:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | protected Class> loadClass(String name, boolean resolve) ????????throws ClassNotFoundException ????{ ????????synchronized (getClassLoadingLock(name)) { ????????????// First, check if the class has already been loaded ????????????Class c = findLoadedClass(name); ????????????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 ????????????????} ????????????????if (c == null) { ????????????????????// If still not found, then invoke findClass in order ????????????????????// to find the class. ????????????????????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; ????????} ????} |
還是來(lái)看sun公司對(duì)該方法的解釋:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /** ?????* Loads the class with the specified binary name.? The ?????* default implementation of this method searches for classes in the ?????* following order: ?????* ?????* ?????* ?????*??? Invoke {<a href="http://www.jobbole.com/members/57845349">@link</a> #findLoadedClass(String)} to check if the class ?????*?? has already been loaded.? ?????* ?????*??? Invoke the {<a href="http://www.jobbole.com/members/57845349">@link</a> #loadClass(String) loadClass} method ?????*?? on the parent class loader.? If the parent is null the class ?????*?? loader built-in to the virtual machine is used, instead.? ?????* ?????*??? Invoke the {<a href="http://www.jobbole.com/members/57845349">@link</a> #findClass(String)} method to find the ?????*?? class.? ?????* ?????* ?????* ?????*? If the class was found using the above steps, and the ?????* resolve flag is true, this method will then invoke the {<a href="http://www.jobbole.com/members/57845349">@link</a> ?????* #resolveClass(Class)} method on the resulting Class object. ?????* ?????*? Subclasses of ClassLoader are encouraged to override {<a href="http://www.jobbole.com/members/57845349">@link</a> ?????* #findClass(String)}, rather than this method.? ?????* ?????*? Unless overridden, this method synchronizes on the result of ?????* {<a href="http://www.jobbole.com/members/57845349">@link</a> #getClassLoadingLock getClassLoadingLock} method ?????* during the entire class loading process. ?????* ?????*/ |
大致內(nèi)容如下:
使用指定的二進(jìn)制名稱(chēng)來(lái)加載類(lèi),這個(gè)方法的默認(rèn)實(shí)現(xiàn)按照以下順序查找類(lèi): 調(diào)用findLoadedClass(String)方法檢查這個(gè)類(lèi)是否被加載過(guò) 使用父加載器調(diào)用loadClass(String)方法,如果父加載器為Null,類(lèi)加載器裝載虛擬機(jī)內(nèi)置的加載器調(diào)用findClass(String)方法裝載類(lèi), 如果,按照以上的步驟成功的找到對(duì)應(yīng)的類(lèi),并且該方法接收的resolve參數(shù)的值為true,那么就調(diào)用resolveClass(Class)方法來(lái)處理類(lèi)。?ClassLoader的子類(lèi)最好覆蓋findClass(String)而不是這個(gè)方法。?除非被重寫(xiě),這個(gè)方法默認(rèn)在整個(gè)裝載過(guò)程中都是同步的(線程安全的)
接下來(lái),我們開(kāi)始分析該方法。
**protected Class> loadClass(String name, boolean resolve)** 該方法的訪問(wèn)控制符是`protected`,也就是說(shuō)該方法**同包內(nèi)和派生類(lèi)中可用** 返回值類(lèi)型`Class
>
,這里用到**泛型**。這里使用通配符?作為泛型實(shí)參表示對(duì)象可以 接受任何類(lèi)型(類(lèi)類(lèi)型)。因?yàn)樵摲椒ú恢酪虞d的類(lèi)到底是什么類(lèi),所以就用了通用的泛型。String name要查找的類(lèi)的名字,boolean resolve,一個(gè)標(biāo)志,true表示將調(diào)用resolveClass(c)`處理該類(lèi)
throws ClassNotFoundException?該方法會(huì)拋出找不到該類(lèi)的異常,這是一個(gè)非運(yùn)行時(shí)異常
synchronized (getClassLoadingLock(name))?看到這行代碼,我們能知道的是,這是一個(gè)同步代碼塊,那么synchronized的括號(hào)中放的應(yīng)該是一個(gè)對(duì)象。我們來(lái)看getClassLoadingLock(name)方法的作用是什么:
| 1 2 3 4 5 6 7 8 9 10 11 | protected Object getClassLoadingLock(String className) { ????????Object lock = this; ????????if (parallelLockMap != null) { ????????????Object newLock = new Object(); ????????????lock = parallelLockMap.putIfAbsent(className, newLock); ????????????if (lock == null) { ????????????????lock = newLock; ????????????} ????????} ????????return lock; ????} |
以上是getClassLoadingLock(name)方法的實(shí)現(xiàn)細(xì)節(jié),我們看到這里用到變量parallelLockMap?,根據(jù)這個(gè)變量的值進(jìn)行不同的操作,如果這個(gè)變量是Null,那么直接返回this,如果這個(gè)屬性不為Null,那么就新建一個(gè)對(duì)象,然后在調(diào)用一個(gè)putIfAbsent(className, newLock);方法來(lái)給剛剛創(chuàng)建好的對(duì)象賦值,這個(gè)方法的作用我們一會(huì)講。那么這個(gè)parallelLockMap變量又是哪來(lái)的那,我們發(fā)現(xiàn)這個(gè)變量是ClassLoader類(lèi)的成員變量:
| 1 | private final ConcurrentHashMap parallelLockMap; |
這個(gè)變量的初始化工作在ClassLoader的構(gòu)造函數(shù)中:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | private ClassLoader(Void unused, ClassLoader parent) { ????????this.parent = parent; ????????if (ParallelLoaders.isRegistered(this.getClass())) { ????????????parallelLockMap = new ConcurrentHashMap(); ????????????package2certs = new ConcurrentHashMap(); ????????????domains = ????????????????Collections.synchronizedSet(new HashSet()); ????????????assertionLock = new Object(); ????????} else { ????????????// no finer-grained lock; lock on the classloader instance ????????????parallelLockMap = null; ????????????package2certs = new Hashtable(); ????????????domains = new HashSet(); ????????????assertionLock = this; ????????} ????} |
這里我們可以看到構(gòu)造函數(shù)根據(jù)一個(gè)屬性ParallelLoaders的Registered狀態(tài)的不同來(lái)給parallelLockMap?賦值。 我去,隱藏的好深,好,我們繼續(xù)挖,看看這個(gè)ParallelLoaders又是在哪賦值的呢?我們發(fā)現(xiàn),在ClassLoader類(lèi)中包含一個(gè)靜態(tài)內(nèi)部類(lèi)private static class ParallelLoaders,在ClassLoader被加載的時(shí)候這個(gè)靜態(tài)內(nèi)部類(lèi)就被初始化。這個(gè)靜態(tài)內(nèi)部類(lèi)的代碼我就不貼了,直接告訴大家什么意思,sun公司是這么說(shuō)的:Encapsulates the set of parallel capable loader types,意識(shí)就是說(shuō):封裝了并行的可裝載的類(lèi)型的集合。
上面這個(gè)說(shuō)的是不是有點(diǎn)亂,那讓我們來(lái)整理一下: 首先,在ClassLoader類(lèi)中有一個(gè)靜態(tài)內(nèi)部類(lèi)ParallelLoaders,他會(huì)指定的類(lèi)的并行能力,如果當(dāng)前的加載器被定位為具有并行能力,那么他就給parallelLockMap定義,就是new一個(gè)?ConcurrentHashMap(),那么這個(gè)時(shí)候,我們知道如果當(dāng)前的加載器是具有并行能力的,那么parallelLockMap就不是Null,這個(gè)時(shí)候,我們判斷parallelLockMap是不是Null,如果他是null,說(shuō)明該加載器沒(méi)有注冊(cè)并行能力,那么我們沒(méi)有必要給他一個(gè)加鎖的對(duì)象,getClassLoadingLock方法直接返回this,就是當(dāng)前的加載器的一個(gè)實(shí)例。如果這個(gè)parallelLockMap不是null,那就說(shuō)明該加載器是有并行能力的,那么就可能有并行情況,那就需要返回一個(gè)鎖對(duì)象。然后就是創(chuàng)建一個(gè)新的Object對(duì)象,調(diào)用parallelLockMap的putIfAbsent(className, newLock)方法,這個(gè)方法的作用是:首先根據(jù)傳進(jìn)來(lái)的className,檢查該名字是否已經(jīng)關(guān)聯(lián)了一個(gè)value值,如果已經(jīng)關(guān)聯(lián)過(guò)value值,那么直接把他關(guān)聯(lián)的值返回,如果沒(méi)有關(guān)聯(lián)過(guò)值的話(huà),那就把我們傳進(jìn)來(lái)的Object對(duì)象作為value值,className作為Key值組成一個(gè)map返回。然后無(wú)論putIfAbsent方法的返回值是什么,都把它賦值給我們剛剛生成的那個(gè)Object對(duì)象。 這個(gè)時(shí)候,我們來(lái)簡(jiǎn)單說(shuō)明一下getClassLoadingLock(String className)的作用,就是: 為類(lèi)的加載操作返回一個(gè)鎖對(duì)象。為了向后兼容,這個(gè)方法這樣實(shí)現(xiàn):如果當(dāng)前的classloader對(duì)象注冊(cè)了并行能力,方法返回一個(gè)與指定的名字className相關(guān)聯(lián)的特定對(duì)象,否則,直接返回當(dāng)前的ClassLoader對(duì)象。
Class c = findLoadedClass(name);?在這里,在加載類(lèi)之前先調(diào)用findLoadedClass方法檢查該類(lèi)是否已經(jīng)被加載過(guò),findLoadedClass會(huì)返回一個(gè)Class類(lèi)型的對(duì)象,如果該類(lèi)已經(jīng)被加載過(guò),那么就可以直接返回該對(duì)象(在返回之前會(huì)根據(jù)resolve的值來(lái)決定是否處理該對(duì)象,具體的怎么處理后面會(huì)講)。 如果,該類(lèi)沒(méi)有被加載過(guò),那么執(zhí)行以下的加載過(guò)程
| 1 2 3 4 5 6 7 8 9 10 | 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 } |
如果父加載器不為空,那么調(diào)用父加載器的loadClass方法加載類(lèi),如果父加載器為空,那么調(diào)用虛擬機(jī)的加載器來(lái)加載類(lèi)。
如果以上兩個(gè)步驟都沒(méi)有成功的加載到類(lèi),那么
| 1 | c = findClass(name); |
調(diào)用自己的findClass(name)方法來(lái)加載類(lèi)。
這個(gè)時(shí)候,我們已經(jīng)得到了加載之后的類(lèi),那么就根據(jù)resolve的值決定是否調(diào)用resolveClass方法。resolveClass方法的作用是:
鏈接指定的類(lèi)。這個(gè)方法給Classloader用來(lái)鏈接一個(gè)類(lèi),如果這個(gè)類(lèi)已經(jīng)被鏈接過(guò)了,那么這個(gè)方法只做一個(gè)簡(jiǎn)單的返回。否則,這個(gè)類(lèi)將被按照?Java?規(guī)范中的Execution描述進(jìn)行鏈接……
至此,ClassLoader類(lèi)以及l(fā)oadClass方法的源碼我們已經(jīng)分析完了,那么。結(jié)合源碼的分析,我們來(lái)總結(jié)一下:
總結(jié)
java中的類(lèi)大致分為三種:
1.系統(tǒng)類(lèi) 2.擴(kuò)展類(lèi) 3.由程序員自定義的類(lèi)
類(lèi)裝載方式,有兩種:
1.隱式裝載, 程序在運(yùn)行過(guò)程中當(dāng)碰到通過(guò)new 等方式生成對(duì)象時(shí),隱式調(diào)用類(lèi)裝載器加載對(duì)應(yīng)的類(lèi)到j(luò)vm中。 2.顯式裝載, 通過(guò)class.forname()等方法,顯式加載需要的類(lèi)
類(lèi)加載的動(dòng)態(tài)性體現(xiàn):
一個(gè)應(yīng)用程序總是由n多個(gè)類(lèi)組成,Java程序啟動(dòng)時(shí),并不是一次把所有的類(lèi)全部加載后再運(yùn)行,它總是先把保證程序運(yùn)行的基礎(chǔ)類(lèi)一次性加載到j(luò)vm中,其它類(lèi)等到j(luò)vm用到的時(shí)候再加載,這樣的好處是節(jié)省了內(nèi)存的開(kāi)銷(xiāo),因?yàn)閖ava最早就是為嵌入式系統(tǒng)而設(shè)計(jì)的,內(nèi)存寶貴,這是一種可以理解的機(jī)制,而用到時(shí)再加載這也是java動(dòng)態(tài)性的一種體現(xiàn)
java類(lèi)裝載器
| 1 2 3 4 5 6 7 8 9 10 11 | Java中的類(lèi)裝載器實(shí)質(zhì)上也是類(lèi),功能是把類(lèi)載入jvm中,值得注意的是jvm的類(lèi)裝載器并不是一個(gè),而是三個(gè),層次結(jié)構(gòu)如下: ??Bootstrap Loader? - 負(fù)責(zé)加載系統(tǒng)類(lèi) ????????| ??????- - ExtClassLoader? - 負(fù)責(zé)加載擴(kuò)展類(lèi) ????????????????| ???????????????- - AppClassLoader? - 負(fù)責(zé)加載應(yīng)用類(lèi) |
為什么要有三個(gè)類(lèi)加載器,一方面是分工,各自負(fù)責(zé)各自的區(qū)塊,另一方面為了實(shí)現(xiàn)委托模型,下面會(huì)談到該模型
類(lèi)加載器之間是如何協(xié)調(diào)工作的
前面說(shuō)了,java中有三個(gè)類(lèi)加載器,問(wèn)題就來(lái)了,碰到一個(gè)類(lèi)需要加載時(shí),它們之間是如何協(xié)調(diào)工作的,即java是如何區(qū)分一個(gè)類(lèi)該由哪個(gè)類(lèi)加載器來(lái)完成呢。 在這里java采用了委托模型機(jī)制,這個(gè)機(jī)制簡(jiǎn)單來(lái)講,就是“類(lèi)裝載器有載入類(lèi)的需求時(shí),會(huì)先請(qǐng)示其Parent使用其搜索路徑幫忙載入,如果Parent 找不到,那么才由自己依照自己的搜索路徑搜索類(lèi)”
下面舉一個(gè)例子來(lái)說(shuō)明,為了更好的理解,先弄清楚幾行代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Public class Test{ ????Public static void main(String[] arg){ ??????ClassLoader c? = Test.class.getClassLoader();? //獲取Test類(lèi)的類(lèi)加載器 ????????System.out.println(c); ??????ClassLoader c1 = c.getParent();? //獲取c這個(gè)類(lèi)加載器的父類(lèi)加載器 ????????System.out.println(c1); ??????ClassLoader c2 = c1.getParent();//獲取c1這個(gè)類(lèi)加載器的父類(lèi)加載器 ????????System.out.println(c2); ??} } |
運(yùn)行結(jié)果:
| 1 2 3 4 5 | ……AppClassLoader…… ……ExtClassLoader…… Null |
可以看出Test是由AppClassLoader加載器加載的,AppClassLoader的Parent?加載器是?ExtClassLoader,但是ExtClassLoader的Parent為?null?是怎么回事呵,朋友們留意的話(huà),前面有提到Bootstrap Loader是用C++語(yǔ)言寫(xiě)的,依java的觀點(diǎn)來(lái)看,邏輯上并不存在Bootstrap Loader的類(lèi)實(shí)體,所以在java程序代碼里試圖打印出其內(nèi)容時(shí),我們就會(huì)看到輸出為null。
類(lèi)裝載器ClassLoader(一個(gè)抽象類(lèi))描述一下JVM加載class文件的原理機(jī)制
類(lèi)裝載器就是尋找類(lèi)或接口字節(jié)碼文件進(jìn)行解析并構(gòu)造JVM內(nèi)部對(duì)象表示的組件,在java中類(lèi)裝載器把一個(gè)類(lèi)裝入JVM,經(jīng)過(guò)以下步驟:
1、裝載:查找和導(dǎo)入Class文件 2、鏈接:其中解析步驟是可以選擇的 (a)檢查:檢查載入的class文件數(shù)據(jù)的正確性 (b)準(zhǔn)備:給類(lèi)的靜態(tài)變量分配存儲(chǔ)空間 (c)解析:將符號(hào)引用轉(zhuǎn)成直接引用 3、初始化:對(duì)靜態(tài)變量,靜態(tài)代碼塊執(zhí)行初始化工作
類(lèi)裝載工作由ClassLoder和其子類(lèi)負(fù)責(zé)。JVM在運(yùn)行時(shí)會(huì)產(chǎn)生三個(gè)ClassLoader:根裝載器,ExtClassLoader(擴(kuò)展類(lèi)裝載器)和AppClassLoader,其中根裝載器不是ClassLoader的子類(lèi),由C++編寫(xiě),因此在java中看不到他,負(fù)責(zé)裝載JRE的核心類(lèi)庫(kù),如JRE目錄下的rt.jar,charsets.jar等。ExtClassLoader是ClassLoder的子類(lèi),負(fù)責(zé)裝載JRE擴(kuò)展目錄ext下的jar類(lèi)包;AppClassLoader負(fù)責(zé)裝載classpath路徑下的類(lèi)包,這三個(gè)類(lèi)裝載器存在父子層級(jí)關(guān)系****,即根裝載器是ExtClassLoader的父裝載器,ExtClassLoader是AppClassLoader的父裝載器。默認(rèn)情況下使用AppClassLoader裝載應(yīng)用程序的類(lèi)
Java裝載類(lèi)使用“全盤(pán)負(fù)責(zé)委托機(jī)制”。“全盤(pán)負(fù)責(zé)”是指當(dāng)一個(gè)ClassLoder裝載一個(gè)類(lèi)時(shí),除非顯示的使用另外一個(gè)ClassLoder,該類(lèi)所依賴(lài)及引用的類(lèi)也由這個(gè)ClassLoder載入;“委托機(jī)制”是指先委托父類(lèi)裝載器尋找目標(biāo)類(lèi),只有在找不到的情況下才從自己的類(lèi)路徑中查找并裝載目標(biāo)類(lèi)。這一點(diǎn)是從安全方面考慮的,試想如果一個(gè)人寫(xiě)了一個(gè)惡意的基礎(chǔ)類(lèi)(如java.lang.String)并加載到JVM將會(huì)引起嚴(yán)重的后果,但有了全盤(pán)負(fù)責(zé)制,java.lang.String永遠(yuǎn)是由根裝載器來(lái)裝載,避免以上情況發(fā)生 除了JVM默認(rèn)的三個(gè)ClassLoder以外,第三方可以編寫(xiě)自己的類(lèi)裝載器,以實(shí)現(xiàn)一些特殊的需求。類(lèi)文件被裝載解析后,在JVM中都有一個(gè)對(duì)應(yīng)的java.lang.Class對(duì)象,提供了類(lèi)結(jié)構(gòu)信息的描述。數(shù)組,枚舉及基本數(shù)據(jù)類(lèi)型,甚至void都擁有對(duì)應(yīng)的Class對(duì)象。Class類(lèi)沒(méi)有public的構(gòu)造方法,Class對(duì)象是在裝載類(lèi)時(shí)由JVM通過(guò)調(diào)用類(lèi)裝載器中的defineClass()方法自動(dòng)構(gòu)造的。
http://blog.jobbole.com/96145/?utm_source=hao.jobbole.com&utm_medium=relatedArticle
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的深度分析 Java 的 ClassLoader 机制(源码级别)(转)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: UITableView 禁止下拉
- 下一篇: FZU2095 水面高度