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

如何加載一個Class文件

在之前的文章中吏祸,筆者介紹了Java虛擬機(jī)--類加載機(jī)制对蒲,闡述了一個類加載到底做了哪些事情!

但是贡翘,關(guān)于類加載器的只是蹈矮,并沒有做任何介紹,只是說了下會在后面的文章中進(jìn)行單獨(dú)闡述含滴。那么丐巫,本篇的意義就是來告訴大家類加載器的實(shí)現(xiàn)。

首先碑韵,我們來簡單的回顧下類加載機(jī)制中的內(nèi)容缎脾。

類加載機(jī)制

虛擬機(jī)把類的數(shù)據(jù)從.class文件加載到內(nèi)存,并對class文件中的數(shù)據(jù)進(jìn)行校驗联喘、轉(zhuǎn)換、解析叭喜、初始化等操作后蓖谢,最終形成可以被虛擬機(jī)識別并使用的Class對象的過程就叫做“虛擬機(jī)的類加載”,主要包括為3大階段啥辨。

類加載機(jī)制

階段一:加載

加載溉知,類加載器通過類的全限定名來獲取類的二進(jìn)制字節(jié)流腊嗡,獲取的方式可以通過jar包、war包卡者、網(wǎng)絡(luò)客们、JSP文件中獲取,絕大部分情況下是通過jar包恒傻、war包中獲取建邓。

獲取到字節(jié)流后,會將字節(jié)流中的信息轉(zhuǎn)化為方法區(qū)中的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)官边。在內(nèi)存中,生成代表該類的Class對象契吉,作為訪問該類的數(shù)據(jù)入口捐晶。

階段二:連接

連接比較復(fù)雜,分為3個小階段:

驗證:確保被加載類的正確性惑灵,即確保被加載的類符合javac編譯的規(guī)范,可編譯通過的代碼胶哲。

準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存潭辈,并初始化為默認(rèn)值(零值)把敢。

解析:將類中的符號引用轉(zhuǎn)化為直接引用谅辣。

階段三:初始化

為類的靜態(tài)變量賦值,與連接階段中的的準(zhǔn)備不同柏副。此階段蚣录,代碼可debug查看。

如int類型的靜態(tài)變量static int x = 3荔泳,連接階段賦零值即為0虐杯,而初始化階段賦值即為3。

以上就是類加載機(jī)制的三大階段支子,而我們今天要將的類加載器存在于階段一中--加載达舒。可以說吞歼,沒有類加載器也就沒有了后續(xù)的流程塔猾,類加載器在Java虛擬機(jī)中起到了至關(guān)重要的作用。

類加載器

類加載器(class loader)將Java類從本地磁盤加載到Java虛擬機(jī)中糯俗,并同時創(chuàng)建了該類的Class對象,實(shí)現(xiàn)了“通過一個類的全限定類名來獲取此類的二進(jìn)制字節(jié)流”功能得湘。

類加載器是Java語言的一項創(chuàng)新,也是Java語言流程的重要原因之一摆马,在類層次劃分囤采、OSGI惩淳、熱部署、代碼加密等領(lǐng)域有著重要的作用代虾,成為Java不可或缺的一部分激蹲。

首先,我們來寫一個測試類含蓉,來看下類加載器项郊,ClassLoaderTest測試類:

public class ClassLoaderTest { 
   public static void main(String[] args) { 
       ClassLoader loader = ClassLoaderTest.class.getClassLoader(); 
       while (true) { 
           System.out.println(loader);
            if(loader==null){
                break;
            }
            loader = loader.getParent();
       } 
   } 
}

運(yùn)行結(jié)果:

sun.misc.Launcher$AppClassLoader@41dee0d7

sun.misc.Launcher$ExtClassLoader@f7b650a

null

首先獲取到的是AppClassLoader類加載器着降,緊接著又獲取的是ExtClassLoader類加載器,最后獲取的對象為null

為什么為null呢任洞,后續(xù)來解答交掏!

接下來,我們來看看在Java體系中到底有哪些類加載器钱骂。

類加載的分類

在Java中,類加載器可以分為兩大類见秽,一類是由Java系統(tǒng)提供的,另外一類是自定義的步责,由開發(fā)人員編寫提供的禀苦。

系統(tǒng)類加載器:

引導(dǎo)類加載器(bootstrap class loader):用來加載Java的核心庫,由于引導(dǎo)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié)省核,開發(fā)者無法直接獲取到啟動類加載器的引用,所以不允許直接通過引用進(jìn)行操作,不繼承自java.lang.ClassLoader(這就是上面例子中為什么最后取到的對象為null的原因)邻储。負(fù)責(zé)加載<Java_Runtime_Home>/lib下面的類庫到內(nèi)存中吨娜,或-Xbootclasspath選項指定的jar包裝入內(nèi)存;

擴(kuò)展類加載器(extensions class loader):用來加載Java的擴(kuò)展庫陪毡,由sun.misc.Launcher$ExtClassLoader來實(shí)現(xiàn)。負(fù)責(zé)加載
<Java_Runtime_Home>/lib/ext毡琉,或-Djava.ext.dirs選項指定目錄下的jar包裝入到內(nèi)存中桅滋;

系統(tǒng)類加載器(system class loader):用來加載Java應(yīng)用的類路徑(CLASSPATH)的Java類,由sun.misc.Launcher$AppClassLoader來實(shí)現(xiàn)丐谋。一般來說煌珊,Java應(yīng)用中的類都是由它來完成加載的,可以通過ClassLoader.getSystemClassLoader()來獲取吏饿。

自定義類加載器:

自定義類加載器(User Custom ClassLoader):開發(fā)人員可以通過繼承java.lang.ClassLoader類的方式實(shí)現(xiàn)自己的類加載器,以滿足一些特殊的需求陨倡。在程序運(yùn)行期間, 通過自定義的java.lang.ClassLoader子類動態(tài)加載class文件。

java.lang.ClassLoader類介紹

方法 說明
getParent() 返回該類加載器的父類加載器兴革。
loadClass(String name) 加載名稱為 name的類杂曲,返回的結(jié)果是 java.lang.Class類的實(shí)例袁余。
findClass(String name) 查找名稱為 name的類,返回的結(jié)果是 java.lang.Class類的實(shí)例棚饵。
findLoadedClass(String name) 查找名稱為 name的已經(jīng)被加載過的類掩完,返回的結(jié)果是 java.lang.Class類的實(shí)例。
defineClass(String name, byte[] b, int off, int len) 把字節(jié)數(shù)組 b中的內(nèi)容轉(zhuǎn)換成 Java 類欣硼,返回的結(jié)果是 java.lang.Class類的實(shí)例恶阴。這個方法被聲明為 final的。
resolveClass(Class<?> c) 鏈接指定的 Java 類焦匈。

以上為ClassLoader對于類加載功能的主要方法介紹括授。

在我們的應(yīng)用程序中岩饼,都是由這4種類加載器互相配合進(jìn)行加載,這4種類加載器在虛擬機(jī)中維護(hù)了一種父子關(guān)系版述,這種關(guān)系叫做“雙親委派模型”寞冯。下面渴析,我們就來看看什么是雙親委派模型俭茧。

雙親委派模型

下面的圖片中,展示的就是“雙親委派模型”午磁,模型中呈現(xiàn)出Java體系架構(gòu)中的四大類加載器的關(guān)系毡们,除了頂層的引導(dǎo)類加載器之外,其余類加載都需要有父加載器存在衙熔,但是此子父類關(guān)系并不是通過java代碼中繼承的方式實(shí)現(xiàn)红氯。具體如何實(shí)現(xiàn),后面講解痢甘。

1526024942(1).png

知道了類加載器的結(jié)構(gòu)模型产阱,那么該模型在代碼整個Java體系中如何工作呢构蹬?

工作流程:一個類加載器收到了類加載請求庄敛,它首先不會自己去嘗試加載這個類,而是把這個類加載請求委派給其父類加載器去完成藻烤,每一個層的類加載器都是如此头滔,依次向父類加載器傳遞,最終所有的類加載請求都會傳送到頂層的啟動類加載器(bootstrap)中兴猩,只有當(dāng)父加載器反饋無法完成這個類加載請求時早歇,子類加載器才會嘗試自己去進(jìn)行類加載操作讨勤,如果子類加載器也依舊無法完成潭千,則代碼層面就會拋出異常刨晴。

此時垛玻,你會不會感到疑惑?為啥兒子自己的活不去干帚桩,而首先交給他爹去完成呢账嚎?這么做的目的何在?

在Java體系中疼邀,雙親委派模型保證了類的唯一性,將Java類與它的類加載器綁定到了一起旁振,當(dāng)父類加載器加載完成后拐袜,子類加載器不會再次加載梢薪。此外,雙親委派模型還保證了Java框架的安全性甜攀。例如:java.lang.Object類琐馆,無論是上述哪個類加載器要加載這個類,最終都會委派給模型中的啟動類加載器去加載姥敛,因此java.lang.Object類在程序中保證了唯一性彤敛。

相反与帆,如果沒有使用該模型玄糟,而是由各個類加載器自行去加載的話阵翎,那么系統(tǒng)中就會出現(xiàn)不同的java.lang.Object類,類的唯一性被打破郭卫,Java體系中的基本行為就得不到保證背稼。例如:,比較兩個類是否“相等”词疼,只有在這兩個類是由同一個類加載器加載的前提下才有意義帘腹。否則,即使兩個類來源于同一個Class文件舵盈,被同一個虛擬機(jī)加載秽晚,只要加載他們的類加載器不同,那這兩個類就必定不相等。涉及到“類相等”的方法有:Class對象的equals()方法锨能、isAssignableFrom()方法址遇、isInstance()方法以及instanceof對象所屬關(guān)系判定斋竞。

試想一下,如果我們自定義一個java.lang.Object類會怎么樣浸剩?(其實(shí)我們自定義的java.lang.Object類無法在程序中被導(dǎo)入,只能模擬定義java.lang.Object類--java.lang.ObjectTest)

當(dāng)JVM請求類加載進(jìn)行自定義的類加載時吏恭,雙親委派模型會將請求傳遞到啟動類加載器中重罪,但是啟動類加載器默認(rèn)只加載<Java_Runtime_Home>/lib路徑下的類,在該路徑下并沒有ObjectTest類搅幅,所以啟動類加載器無法加載茄唐,只能向下傳遞給子類加載器砸讳,最終會將請求傳遞到系統(tǒng)類加載器中簿寂,但是系統(tǒng)類加載器也無法進(jìn)行加載,會拋出異常常遂。

為什么克胳,why?

這是因為以java.開頭的是核心API包捏雌,需要訪問權(quán)限笆搓,強(qiáng)制加載會拋出異常,任何以java.開頭的包都會報錯:

Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
image

此異常是代碼層面拋出的肤频,并不是native方法虛擬機(jī)底層拋出宵荒,源碼可見(ClassLoader類):

if ((name != null) && name.startsWith("java.")) {
        throw new SecurityException
            ("Prohibited package name: " +
             name.substring(0, name.lastIndexOf('.')));
}

此時,你會不會又突發(fā)奇想侠讯,我自己定義一個類少孝,放在<Java_Runtime_Home>/lib路徑下會如何?

image

編寫代碼袁翁,并打成jar包粱胜,jar包的名稱就叫做 jiaboyan.jar:

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

接下來狐树,把jiaboyan.jar包放入到<Java_Runtime_Home>/lib下抑钟,也同時放入到<Java_Runtime_Home>/lib/ext,并同時保留截圖中的代碼幻件,如圖所示:

image

執(zhí)行main()方法绰沥,結(jié)果如下:

sun.misc.Launcher$AppClassLoader@8fd9b4d

從輸出可以看出贺待,放置到<Java_Runtime_Home>/lib目錄下的ObjectTest.class類并沒有被啟動類加載器加載徽曲,而是由擴(kuò)展類加載器加載了。

why麸塞?不是說了委派給最頂層的類加載進(jìn)行加載嗎秃臣?其實(shí),這是由于虛擬機(jī)出于安全角度考慮哪工,不會加載<Java_Runtime_Home>/lib中的陌生類奥此,開發(fā)者把自定義的類放置到此目錄下啟動類加載器是不會進(jìn)行加載的。

下一篇正勒,將會對類加載器源碼進(jìn)行分析5迷骸I迪场章贞!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蜕径,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赡麦,老刑警劉巖遂铡,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钾怔,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)凝垛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門桃焕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來让网,“玉大人溃睹,你說我怎么就攤上這事【鹤遥” “怎么了锯茄?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵抓半,是天一觀的道長琅关。 經(jīng)常有香客問我画机,道長步氏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮泡躯,結(jié)果婚禮上较剃,老公的妹妹穿的比我還像新娘。我一直安慰自己啊送,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著款票,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缚够。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天陪踩,我揣著相機(jī)與錄音摘完,去河邊找鬼描焰。 笑死,一個胖子當(dāng)著我的面吹牛步绸,可吹牛的內(nèi)容都是我干的吕喘。 我是一名探鬼主播氯质,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼琢锋,長吁一口氣:“原來是場噩夢啊……” “哼钉嘹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鸟悴,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹏浅,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡友浸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了硼补。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡栏妖,死狀恐怖吊趾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布识颊,位于F島的核電站,受9級特大地震影響函筋,放射性物質(zhì)發(fā)生泄漏跌帐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸蛹磺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春挽封,著一層夾襖步出監(jiān)牢的瞬間辅愿,已是汗流浹背阔蛉。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工颠区, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人磨澡。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓胆描,卻偏偏與公主長得像昌讲,于是被迫代替她去往敵國和親醋闭。 傳聞我的和親對象是個殘疾皇子囚企,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

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