深度分析Java的ClassLoader机制(源码级别)
轉(zhuǎn)載自?深度分析Java的ClassLoader機制(源碼級別)
Java中的所有類,必須被裝載到jvm中才能運行,這個裝載工作是由jvm中的類裝載器完成的,類裝載器所做的工作實質(zhì)是把類文件從硬盤讀取到內(nèi)存中,JVM在加載類的時候,都是通過ClassLoader的loadClass()方法來加載class的,loadClass使用雙親委派模式。
為了更好的理解類的加載機制,我們來深入研究一下ClassLoader和他的loadClass()方法。
源碼分析
先來認識下我們今天的主角?public abstract class ClassLoader類,他是一個抽象類,sun公司是這么解釋這個類的:
大致意思如下:
類加載器(class loader)是一個負責加載JAVA類(classes)的對象,ClassLoader類是一個抽象類,需要給出類的二進制名稱,class loader嘗試定位或者產(chǎn)生一個class的數(shù)據(jù),一個典型的策略是把二進制名字轉(zhuǎn)換成文件名然后到文件系統(tǒng)中找到該文件。
接下來我們看loadClass方法的實現(xiàn)方式:
這個是整個方法的實現(xiàn),后面我們會對他做分解,還是來看sun公司對該方法的解釋:
大致內(nèi)容如下:
使用指定的二進制名稱來加載類,這個方法的默認實現(xiàn)按照以下順序查找類:?
調(diào)用findLoadedClass(String)方法檢查這個類是否被加載過?
使用父加載器調(diào)用loadClass(String)方法,如果父加載器為Null,類加載器裝載虛擬機內(nèi)置的加載器
調(diào)用findClass(String)方法裝載類
如果,按照以上的步驟成功的找到對應的類,并且該方法接收的resolve參數(shù)的值為true,那么就調(diào)用resolveClass(Class)方法來處理類。??ClassLoader的子類最好覆蓋findClass(String)而不是這個方法(loadClass)。?除非被重寫,這個方法默認在整個裝載過程中都是同步的(線程安全的)
接下來,我們開始分析該方法。
protected Class<?> loadClass(String name, boolean resolve)?該方法的訪問控制符是protected,也就是說該方法同包內(nèi)和派生類中可用。返回值類型Class <?>,這里用到泛型。這里使用通配符?作為泛型實參表示對象可以接受任何類型(類類型)。因為該方法不知道要加載的類到底是什么類,所以就用了通用的泛型。String name要查找的類的名字,boolean resolve,一個標志,true表示將調(diào)用resolveClass(c)處理該類
throws ClassNotFoundException?該方法會拋出找不到該類的異常,這是一個非運行時異常
synchronized (getClassLoadingLock(name))?看到這行代碼,我們能知道的是,這是一個同步代碼塊,那么synchronized的括號中放的應該是一個對象。我們來看getClassLoadingLock(name)方法的作用是什么:
以上是getClassLoadingLock(name)方法的實現(xiàn)細節(jié),我們看到這里用到變量parallelLockMap,根據(jù)這個變量的值進行不同的操作,如果這個變量是Null,那么直接返回this,如果這個屬性不為Null,那么就新建一個對象,然后在調(diào)用一個putIfAbsent(className, newLock);方法來給剛剛創(chuàng)建好的對象賦值,這個方法的作用我們一會講。那么這個parallelLockMap變量又是哪來的那,我們發(fā)現(xiàn)這個變量是ClassLoader類的成員變量:
private final ConcurrentHashMap<String, Object> parallelLockMap;這個變量的初始化工作在ClassLoader的構(gòu)造函數(shù)中:
這里我們可以看到構(gòu)造函數(shù)根據(jù)一個屬性ParallelLoaders的Registered狀態(tài)的不同來給parallelLockMap?賦值。 我去,隱藏的好深,好,我們繼續(xù)挖,看看這個ParallelLoaders又是在哪賦值的呢?我們發(fā)現(xiàn),在ClassLoader類中包含一個靜態(tài)內(nèi)部類private static class ParallelLoaders,在ClassLoader被加載的時候這個靜態(tài)內(nèi)部類就被初始化。這個靜態(tài)內(nèi)部類的代碼我就不貼了,直接告訴大家什么意思,sun公司是這么說的:Encapsulates the set of parallel capable loader types,意識就是說:封裝了并行的可裝載的類型的集合。
上面這個說的是不是有點亂,那讓我們來整理一下:?
首先,在ClassLoader類中有一個靜態(tài)內(nèi)部類ParallelLoaders,他會指定的類的并行能力,如果當前的加載器被定位為具有并行能力,那么他就給parallelLockMap定義,就是new一個?ConcurrentHashMap<>(),那么這個時候,我們知道如果當前的加載器是具有并行能力的,那么parallelLockMap就不是Null,這個時候,我們判斷parallelLockMap是不是Null,如果他是null,說明該加載器沒有注冊并行能力,那么我們沒有必要給他一個加鎖的對象,getClassLoadingLock方法直接返回this,就是當前的加載器的一個實例。
如果這個parallelLockMap不是null,那就說明該加載器是有并行能力的,那么就可能有并行情況,那就需要返回一個鎖對象。然后就是創(chuàng)建一個新的Object對象,調(diào)用parallelLockMap的putIfAbsent(className, newLock)方法,這個方法的作用是:首先根據(jù)傳進來的className,檢查該名字是否已經(jīng)關(guān)聯(lián)了一個value值,如果已經(jīng)關(guān)聯(lián)過value值,那么直接把他關(guān)聯(lián)的值返回,如果沒有關(guān)聯(lián)過值的話,那就把我們傳進來的Object對象作為value值,className作為Key值組成一個map返回。然后無論putIfAbsent方法的返回值是什么,都把它賦值給我們剛剛生成的那個Object對象。?
這個時候,我們來簡單說明下getClassLoadingLock(String className)的作用,就是: 為類的加載操作返回一個鎖對象。為了向后兼容,這個方法這樣實現(xiàn):如果當前的classloader對象注冊了并行能力,方法返回一個與指定的名字className相關(guān)聯(lián)的特定對象,否則,直接返回當前的ClassLoader對象。
接著,我們的代碼分析到Class c = findLoadedClass(name);?在這里,在加載類之前先調(diào)用該方法檢查該類是否已經(jīng)被加載過,findLoadedClass會返回一個Class類型的對象,如果該類已經(jīng)被加載過,那么就可以直接返回該對象(在返回之前會根據(jù)resolve的值來決定是否處理該對象,具體的怎么處理后面會講)。 如果,該類沒有被加載過,那么執(zhí)行以下的加載過程。
如果父加載器不為空,那么調(diào)用父加載器的loadClass方法加載類,如果父加載器為空,那么調(diào)用虛擬機的加載器來加載類。
如果以上兩個步驟都沒有成功的加載到類。那么執(zhí)行以下過程:
c = findClass(name)表示當前classloader自己來加載類。
這個時候,我們已經(jīng)得到了加載之后的類,那么就根據(jù)resolve的值決定是否調(diào)用resolveClass方法。resolveClass方法的作用是:
鏈接指定的類。這個方法給Classloader用來鏈接一個類,如果這個類已經(jīng)被鏈接過了,那么這個方法只做一個簡單的返回。否則,這個類將被按照?Java?規(guī)范中的Execution描述進行鏈接。。。
至此,ClassLoader類以及l(fā)oadClass方法的源碼我們已經(jīng)分析完了,那么。結(jié)合源碼的分析,我們來總結(jié)一下。
總結(jié)
在總結(jié)之前,我們先來簡單介紹下類加載相關(guān)的知識,Java中的類大致分為三種:
1.系統(tǒng)類?、2.擴展類?、3.由程序員自定義的類
類裝載方式,有兩種:
1.隱式裝載, 程序在運行過程中當碰到通過new 等方式生成對象時,隱式調(diào)用類裝載器加載對應的類到jvm中。?
2.顯式裝載, 通過class.forname()等方法,顯式加載需要的類
類加載的動態(tài)性體現(xiàn):
一個應用程序總是由n多個類組成,Java程序啟動時,并不是一次把所有的類全部加載后再運行,它總是先把保證程序運行的基礎(chǔ)類一次性加載到jvm中,其它類等到jvm用到的時候再加載,這樣的好處是節(jié)省了內(nèi)存的開銷,因為java最早就是為嵌入式系統(tǒng)而設計的,內(nèi)存寶貴,這是一種可以理解的機制,而用到時再加載這也是java動態(tài)性的一種體現(xiàn)
java類裝載器
Java中的類裝載器實質(zhì)上也是類,功能是把類載入jvm中,值得注意的是jvm的類裝載器并不是一個,而是三個,層次結(jié)構(gòu)如下:
為什么要有三個類加載器,一方面是分工,各自負責各自的區(qū)塊,另一方面為了實現(xiàn)委托模型,下面會談到該模型。
類加載器之間是如何協(xié)調(diào)工作的
前面說了,java中有三個類加載器,問題就來了,碰到一個類需要加載時,它們之間是如何協(xié)調(diào)工作的,即java是如何區(qū)分一個類該由哪個類加載器來完成呢。如前面我們分析的代碼一樣,Java采用了委托模型機制來加載類,這個機制簡單來講,就是“類裝載器有載入類的需求時,會先請示其Parent使用其搜索路徑幫忙載入,如果Parent 找不到,那么才由自己依照自己的搜索路徑搜索類,如果搜索不到,則拋出ClassNotFoundException”
類裝載工作由ClassLoder和其子類負責。JVM在運行時會產(chǎn)生三個ClassLoader:根裝載器,ExtClassLoader(擴展類裝載器)和AppClassLoader,其中根裝載器不是ClassLoader的子類,由C++編寫,因此在java中看不到他,負責裝載JRE的核心類庫,如java.*,。ExtClassLoader是ClassLoder的子類,負責裝載JRE擴展目錄ext下的jar類包;AppClassLoader負責裝載classpath路徑下的類包,這三個類裝載器存在父子層級關(guān)系,即根裝載器是ExtClassLoader的父裝載器,ExtClassLoader是AppClassLoader的父裝載器。默認情況下使用AppClassLoader裝載應用程序的類
下面舉一個例子來說明,為了更好的理解,先弄清楚幾行代碼:
運行結(jié)果:
。。。AppClassLoader。。。 。。。ExtClassLoader。。。 Null可以看出Test是由AppClassLoader加載器加載的,AppClassLoader的Parent?加載器是?ExtClassLoader,但是ExtClassLoader的Parent為?null?是怎么回事呵,朋友們留意的話,前面有提到Bootstrap Loader是用C++語言寫的,依java的觀點來看,邏輯上并不存在Bootstrap Loader的類實體,所以在java程序代碼里試圖打印出其內(nèi)容時,我們就會看到輸出為null。
Java裝載類使用“全盤負責委托機制”。“全盤負責”是指當一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類所依賴及引用的類也由這個ClassLoder載入;“委托機制”是指先委托父類裝載器尋找目標類,只有在找不到的情況下才從自己的類路徑中查找并裝載目標類。
這一點是從安全方面考慮的,試想如果一個人寫了一個惡意的基礎(chǔ)類(如java.lang.String)并加載到JVM將會引起嚴重的后果,但有了全盤負責制,java.lang.String永遠是由根裝載器來裝載,避免以上情況發(fā)生 除了JVM默認的三個ClassLoder以外,第三方可以編寫自己的類裝載器,以實現(xiàn)一些特殊的需求。
類文件被裝載解析后,在JVM中都有一個對應的java.lang.Class對象,提供了類結(jié)構(gòu)信息的描述。數(shù)組,枚舉及基本數(shù)據(jù)類型,甚至void都擁有對應的Class對象。Class類沒有public的構(gòu)造方法,Class對象是在裝載類時由JVM通過調(diào)用類裝載器中的defineClass()方法自動構(gòu)造的。?
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎
總結(jié)
以上是生活随笔為你收集整理的深度分析Java的ClassLoader机制(源码级别)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 和平精英只有流畅才能开90帧?
- 下一篇: java美元兑换,(Java实现) 美元