每個(gè)編寫的”.java”拓展名類文件都存儲著需要執(zhí)行的程序邏輯厢破,這些”.java”文件經(jīng)過Java編譯器編譯成拓展名為”.class”的文件缝裁,”.class”文件中保存著Java代碼經(jīng)轉(zhuǎn)換后的虛擬機(jī)指令扫皱,當(dāng)需要使用某個(gè)類時(shí),虛擬機(jī)將會加載它的”.class”文件捷绑,并創(chuàng)建對應(yīng)的class對象韩脑,將class文件加載到虛擬機(jī)的內(nèi)存,這個(gè)過程稱為類加載粹污,這里我們需要了解一下類加載的過程段多,如下:
加載:類加載過程的一個(gè)階段:通過一個(gè)類的完全限定查找此類字節(jié)碼文件,并利用字節(jié)碼文件創(chuàng)建一個(gè)Class對象
驗(yàn)證:目的在于確保Class文件的字節(jié)流中包含信息符合當(dāng)前虛擬機(jī)要求壮吩,不會危害虛擬機(jī)自身安全进苍。主要包括四種驗(yàn)證加缘,文件格式驗(yàn)證,元數(shù)據(jù)驗(yàn)證觉啊,字節(jié)碼驗(yàn)證拣宏,符號引用驗(yàn)證。
準(zhǔn)備:為類變量(即static修飾的字段變量)分配內(nèi)存并且設(shè)置該類變量的初始值即0(如static int i=5;這里只將i初始化為0杠人,至于5的值將在初始化時(shí)賦值)勋乾,這里不包含用final修飾的static,因?yàn)閒inal在編譯的時(shí)候就會分配了嗡善,注意這里不會為實(shí)例變量分配初始化辑莫,類變量會分配在方法區(qū)中,而實(shí)例變量是會隨著對象一起分配到Java堆中罩引。
解析:主要將常量池中的符號引用替換為直接引用的過程各吨。符號引用就是一組符號來描述目標(biāo),可以是任何字面量袁铐,而直接引用就是直接指向目標(biāo)的指針绅你、相對偏移量或一個(gè)間接定位到目標(biāo)的句柄。有類或接口的解析昭躺,字段解析忌锯,類方法解析,接口方法解析(這里涉及到字節(jié)碼變量的引用领炫,如需更詳細(xì)了解偶垮,可參考《深入Java虛擬機(jī)》)。
初始化:類加載最后階段帝洪,若該類具有超類似舵,則對其進(jìn)行初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化成員變量(如前面只初始化了默認(rèn)值的static變量將會在這個(gè)階段賦值葱峡,成員變量也將被初始化)砚哗。
這便是類加載的5個(gè)過程,而類加載器的任務(wù)是根據(jù)一個(gè)類的全限定名來讀取此類的二進(jìn)制字節(jié)流到JVM中砰奕,然后轉(zhuǎn)換為一個(gè)與目標(biāo)類對應(yīng)的java.lang.Class對象實(shí)例蛛芥,在虛擬機(jī)提供了3種類加載器,引導(dǎo)(Bootstrap)類加載器军援、擴(kuò)展(Extension)類加載器仅淑、系統(tǒng)(System)類加載器(也稱應(yīng)用類加載器),下面分別介紹
啟動(Bootstrap)類加載器
啟動類加載器主要加載的是JVM自身需要的類胸哥,這個(gè)類加載使用C++語言實(shí)現(xiàn)的涯竟,是虛擬機(jī)自身的一部分,它負(fù)責(zé)將 <JAVA_HOME>/lib路徑下的核心類庫或-Xbootclasspath參數(shù)指定的路徑下的jar包加載到內(nèi)存中,注意必由于虛擬機(jī)是按照文件名識別加載jar包的庐船,如rt.jar银酬,如果文件名不被虛擬機(jī)識別,即使把jar包丟到lib目錄下也是沒有作用的(出于安全考慮筐钟,Bootstrap啟動類加載器只加載包名為java捡硅、javax、sun等開頭的類)盗棵。
擴(kuò)展(Extension)類加載器
擴(kuò)展類加載器是指Sun公司(已被Oracle收購)實(shí)現(xiàn)的sun.misc.Launcher$ExtClassLoader類,由Java語言實(shí)現(xiàn)的北发,是Launcher的靜態(tài)內(nèi)部類纹因,它負(fù)責(zé)加載<JAVA_HOME>/lib/ext目錄下或者由系統(tǒng)變量-Djava.ext.dir指定位路徑中的類庫舆驶,開發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器瓜浸。
系統(tǒng)(System)類加載器
也稱應(yīng)用程序加載器是指 Sun公司實(shí)現(xiàn)的sun.misc.Launcher$AppClassLoader先嬉。它負(fù)責(zé)加載系統(tǒng)類路徑j(luò)ava -classpath或-D java.class.path 指定路徑下的類庫单旁,也就是我們經(jīng)常用到的classpath路徑顶猜,開發(fā)者可以直接使用系統(tǒng)類加載器塘娶,一般情況下該類加載是程序中默認(rèn)的類加載器扰法,通過ClassLoader#getSystemClassLoader()方法可以獲取到該類加載器栓票。
在Java的日常應(yīng)用程序開發(fā)中密任,類的加載幾乎是由上述3種類加載器相互配合執(zhí)行的颜启,在必要時(shí),我們還可以自定義類加載器浪讳,需要注意的是缰盏,Java虛擬機(jī)對class文件采用的是按需加載的方式,也就是說當(dāng)需要使用該類時(shí)才會將它的class文件加載到內(nèi)存生成class對象淹遵,而且加載某個(gè)類的class文件時(shí)口猜,Java虛擬機(jī)采用的是雙親委派模式即把請求交由父類處理,它一種任務(wù)委派模式透揣,下面我們進(jìn)一步了解它济炎。
雙親委派模式工作原理
雙親委派模式要求除了頂層的啟動類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器辐真,請注意雙親委派模式中的父子關(guān)系并非通常所說的類繼承關(guān)系须尚,而是采用組合關(guān)系來復(fù)用父類加載器的相關(guān)代碼,類加載器間的關(guān)系如下:
雙親委派模式是在Java 1.2后引入的侍咱,其工作原理的是恨闪,如果一個(gè)類加載器收到了類加載請求,它并不會自己先去加載放坏,而是把這個(gè)請求委托給父類的加載器去執(zhí)行咙咽,如果父類加載器還存在其父類加載器,則進(jìn)一步向上委托淤年,依次遞歸钧敞,請求最終將到達(dá)頂層的啟動類加載器蜡豹,如果父類加載器可以完成類加載任務(wù),就成功返回溉苛,倘若父類加載器無法完成此加載任務(wù)镜廉,子加載器才會嘗試自己去加載,這就是雙親委派模式愚战,即每個(gè)兒子都很懶娇唯,每次有活就丟給父親去干,直到父親說這件事我也干不了時(shí)寂玲,兒子自己想辦法去完成塔插,這不就是傳說中的實(shí)力坑爹啊拓哟?那么采用這種模式有啥用呢?
采用雙親委派模式的是好處是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系想许,通過這種層級關(guān)可以避免類的重復(fù)加載,當(dāng)父親已經(jīng)加載了該類時(shí)断序,就沒有必要子ClassLoader再加載一次流纹。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換违诗,假設(shè)通過網(wǎng)絡(luò)傳遞一個(gè)名為java.lang.Integer的類漱凝,通過雙親委托模式傳遞到啟動類加載器,而啟動類加載器在核心Java API發(fā)現(xiàn)這個(gè)名字的類诸迟,發(fā)現(xiàn)該類已被加載碉哑,并不會重新加載網(wǎng)絡(luò)傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class亮蒋,這樣便可以防止核心API庫被隨意篡改扣典。可能你會想慎玖,如果我們在classpath路徑下自定義一個(gè)名為java.lang.SingleInterge類(該類是胡編的)呢贮尖?該類并不存在java.lang中,經(jīng)過雙親委托模式趁怔,傳遞到啟動類加載器中湿硝,由于父類加載器路徑下并沒有該類,所以不會加載润努,將反向委托給子類加載器加載关斜,最終會通過系統(tǒng)類加載器加載該類。但是這樣做是不允許铺浇,因?yàn)閖ava.lang是核心API包痢畜,需要訪問權(quán)限,強(qiáng)制加載將會報(bào)出如下異常
java.lang.SecurityException: Prohibited package name: java.lang
類加載器間的關(guān)系
我們進(jìn)一步了解類加載器間的關(guān)系(并非指繼承關(guān)系),主要可以分為以下4點(diǎn)
啟動類加載器丁稀,由C++實(shí)現(xiàn)吼拥,沒有父類。
拓展類加載器(ExtClassLoader)线衫,由Java語言實(shí)現(xiàn)凿可,父類加載器為null
系統(tǒng)類加載器(AppClassLoader),由Java語言實(shí)現(xiàn)授账,父類加載器為ExtClassLoader
自定義類加載器枯跑,父類加載器肯定為AppClassLoader。