1. java類(lèi)加載器
類(lèi)加載器的任務(wù)是根據(jù)一個(gè)類(lèi)的全限定名來(lái)讀取此類(lèi)的二進(jìn)制字節(jié)流到JVM中凳鬓,然后轉(zhuǎn)換為一個(gè)與目標(biāo)類(lèi)對(duì)應(yīng)的java.lang.Class對(duì)象實(shí)例
在虛擬機(jī)提供了3種類(lèi)加載器,引導(dǎo)(Bootstrap)類(lèi)加載器邀摆、擴(kuò)展(Extension)類(lèi)加載器羔挡、系統(tǒng)(System)類(lèi)加載器(也稱(chēng)應(yīng)用類(lèi)加載器)
1.1 啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader)
啟動(dòng)類(lèi)加載器主要加載的是JVM自身需要的類(lèi)洁奈,這個(gè)類(lèi)加載使用C++語(yǔ)言實(shí)現(xiàn)的,是虛擬機(jī)自身的一部分
它負(fù)責(zé)將 <JAVA_HOME>/lib路徑下的核心類(lèi)庫(kù)或-Xbootclasspath參數(shù)指定的路徑下的jar包加載到內(nèi)存中绞灼,注意必由于虛擬機(jī)是按照文件名識(shí)別加載jar包的利术,如rt.jar,如果文件名不被虛擬機(jī)識(shí)別低矮,即使把jar包丟到lib目錄下也是沒(méi)有作用的(出于安全考慮印叁,Bootstrap啟動(dòng)類(lèi)加載器只加載包名為java、javax军掂、sun等開(kāi)頭的類(lèi))轮蜕。
1.2 擴(kuò)展類(lèi)加載器(Extension ClassLoader)
- 擴(kuò)展類(lèi)加載器是指Sun公司(已被Oracle收購(gòu))實(shí)現(xiàn)的sun.misc.Launcher$ExtClassLoader類(lèi),由Java語(yǔ)言實(shí)現(xiàn)的蝗锥,是Launcher的靜態(tài)內(nèi)部類(lèi)跃洛,它負(fù)責(zé)加載<JAVA_HOME>/lib/ext目錄下或者由系統(tǒng)變量-Djava.ext.dir指定位路徑中的類(lèi)庫(kù),開(kāi)發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類(lèi)加載器终议。
1.3 系統(tǒng)類(lèi)加載器(也稱(chēng)應(yīng)用類(lèi)加載器)(Application ClassLoader)
也稱(chēng)應(yīng)用程序加載器是指 Sun公司實(shí)現(xiàn)的sun.misc.Launcher$AppClassLoader汇竭。它負(fù)責(zé)加載系統(tǒng)類(lèi)路徑j(luò)ava -classpath或-D java.class.path 指定路徑下的類(lèi)庫(kù)葱蝗,也就是我們經(jīng)常用到的classpath路徑,開(kāi)發(fā)者可以直接使用系統(tǒng)類(lèi)加載器细燎,一般情況下該類(lèi)加載是程序中默認(rèn)的類(lèi)加載器两曼,通過(guò)ClassLoader#getSystemClassLoader()方法可以獲取到該類(lèi)加載器。
應(yīng)用程序類(lèi)加載器找颓,該加載器是由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)合愈,該類(lèi)加載器負(fù)責(zé)加載用戶(hù)類(lèi)路徑上所指定的類(lèi)庫(kù)叮贩。開(kāi)發(fā)者可通過(guò)ClassLoader.getSystemClassLoader()方法直接獲取击狮,故又稱(chēng)為系統(tǒng)類(lèi)加載器。當(dāng)應(yīng)用程序沒(méi)有自定義類(lèi)加載器時(shí)益老,默認(rèn)采用該類(lèi)加載器彪蓬。
java.lang.ClassLoader
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader(); //初始化系統(tǒng)類(lèi)加載器 【見(jiàn)下文】
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;
}
1.4 兩個(gè)class對(duì)象是否為同一個(gè)類(lèi)對(duì)象
在JVM中表示兩個(gè)class對(duì)象是否為同一個(gè)類(lèi)對(duì)象存在兩個(gè)必要條件
類(lèi)的完整類(lèi)名必須一致,包括包名捺萌。
加載這個(gè)類(lèi)的ClassLoader(指ClassLoader實(shí)例對(duì)象)必須相同档冬。
也就是說(shuō),在JVM中桃纯,即使這個(gè)兩個(gè)類(lèi)對(duì)象(class對(duì)象)來(lái)源同一個(gè)Class文件酷誓,被同一個(gè)虛擬機(jī)所加載,但只要加載它們的ClassLoader實(shí)例對(duì)象不同态坦,那么這兩個(gè)類(lèi)對(duì)象也是不相等的盐数,這是因?yàn)椴煌腃lassLoader實(shí)例對(duì)象都擁有不同的獨(dú)立的類(lèi)名稱(chēng)空間,所以加載的class對(duì)象也會(huì)存在不同的類(lèi)名空間中伞梯,
1.5 顯示加載與隱式加載
所謂class文件的顯示加載與隱式加載的方式是指JVM加載class文件到內(nèi)存的方式玫氢,
顯示加載指的是在代碼中通過(guò)調(diào)用ClassLoader加載class對(duì)象,如直接使用Class.forName(name)或this.getClass().getClassLoader().loadClass()加載class對(duì)象谜诫。
隱式加載則是不直接在代碼中調(diào)用ClassLoader的方法加載class對(duì)象漾峡,而是通過(guò)虛擬機(jī)自動(dòng)加載到內(nèi)存中,如在加載某個(gè)類(lèi)的class文件時(shí)喻旷,該類(lèi)的class文件中引用了另外一個(gè)類(lèi)的對(duì)象生逸,此時(shí)額外引用的類(lèi)將通過(guò)JVM自動(dòng)加載到內(nèi)存中。
2. 類(lèi)加載的幾個(gè)階段
2.1 類(lèi)加載簡(jiǎn)述
虛擬機(jī)把描述類(lèi)的數(shù)據(jù)從class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn),轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類(lèi)型,這就是虛擬機(jī)類(lèi)的加載機(jī)制
在Java語(yǔ)言里面,類(lèi)型的加載,連接,初始化都是在程序運(yùn)行期間完成的.這種策略雖然稍微增加一點(diǎn)性能開(kāi)銷(xiāo),但是會(huì)為Java應(yīng)用程序提供高度的靈活性,Java天生可以動(dòng)態(tài)擴(kuò)展語(yǔ)言的特性就是依賴(lài)運(yùn)行期間動(dòng)態(tài)加載和動(dòng)態(tài)連接這個(gè)特點(diǎn)實(shí)現(xiàn)的
從上圖可以看且预,java文件通過(guò)編譯器變成了.class文件槽袄,接下來(lái)類(lèi)加載器又將這些.class文件加載到JVM中。其中類(lèi)裝載器的作用其實(shí)就是類(lèi)的加載辣之。今天我們要討論的就是這個(gè)環(huán)節(jié)掰伸。有了這個(gè)印象之后我們?cè)賮?lái)看類(lèi)的加載的概念:
其實(shí)可以一句話來(lái)解釋?zhuān)侯?lèi)的加載指的是將類(lèi)的.class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi)怀估,然后在堆區(qū)創(chuàng)建一個(gè) java.lang.Class對(duì)象狮鸭,用來(lái)封裝類(lèi)在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)合搅。
2.2 類(lèi)加載的幾個(gè)階段
類(lèi)從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到卸載出內(nèi)存為止歧蕉,它的整個(gè)生命周期包括:加載灾部、驗(yàn)證、準(zhǔn)備惯退、解析赌髓、初始化、使用和卸載七個(gè)階段催跪。
它們的順序如下圖所示:其中類(lèi)加載的過(guò)程包括了加載锁蠕、驗(yàn)證、準(zhǔn)備懊蒸、解析荣倾、初始化五個(gè)階段。在這五個(gè)階段中骑丸,加載舌仍、驗(yàn)證、準(zhǔn)備和初始化這四個(gè)階段發(fā)生的順序是確定的通危,而解析階段則不一定铸豁,它在某些情況下可以在初始化階段之后開(kāi)始。另外注意這幾個(gè)階段是按順序開(kāi)始菊碟,而不是按順序進(jìn)行或完成节芥,因?yàn)檫@些階段通常都是互相交叉地混合進(jìn)行的,通常在一個(gè)階段執(zhí)行的過(guò)程中調(diào)用或激活另一個(gè)階段框沟。
2.2.1 加載
”加載“是”類(lèi)加機(jī)制”的第一個(gè)過(guò)程藏古,在加載階段,虛擬機(jī)主要完成三件事:
(1)通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取其定義的二進(jìn)制字節(jié)流
(2)將這個(gè)字節(jié)流所代表的的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
(3)在堆中生成一個(gè)代表這個(gè)類(lèi)的Class對(duì)象忍燥,作為方法區(qū)中這些數(shù)據(jù)的訪問(wèn)入口拧晕。
相對(duì)于類(lèi)加載的其他階段而言,加載階段是可控性最強(qiáng)的階段梅垄,因?yàn)槌绦騿T可以使用系統(tǒng)的類(lèi)加載器加載厂捞,還可以使用自己的類(lèi)加載器加載。我們?cè)谧詈笠徊糠謺?huì)詳細(xì)介紹這個(gè)類(lèi)加載器队丝。在這里我們只需要知道類(lèi)加載器的作用就是上面虛擬機(jī)需要完成的三件事靡馁,僅此而已就好了。
2.2.2 驗(yàn)證
驗(yàn)證的主要作用就是確保被加載的類(lèi)的正確性机久。也是連接階段的第一步臭墨。說(shuō)白了也就是我們加載好的.class文件不能對(duì)我們的虛擬機(jī)有危害,所以先檢測(cè)驗(yàn)證一下膘盖。他主要是完成四個(gè)階段的驗(yàn)證:
(1)文件格式的驗(yàn)證:驗(yàn)證.class文件字節(jié)流是否符合class文件的格式的規(guī)范胧弛,并且能夠被當(dāng)前版本的虛擬機(jī)處理尤误。這里面主要對(duì)魔數(shù)、主版本號(hào)结缚、常量池等等的校驗(yàn)(魔數(shù)损晤、主版本號(hào)都是.class文件里面包含的數(shù)據(jù)信息、在這里可以不用理解)红竭。
(2)元數(shù)據(jù)驗(yàn)證:主要是對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析尤勋,以保證其描述的信息符合java語(yǔ)言規(guī)范的要求,比如說(shuō)驗(yàn)證這個(gè)類(lèi)是不是有父類(lèi)茵宪,類(lèi)中的字段方法是不是和父類(lèi)沖突等等最冰。
(3)字節(jié)碼驗(yàn)證:這是整個(gè)驗(yàn)證過(guò)程最復(fù)雜的階段,主要是通過(guò)數(shù)據(jù)流和控制流分析眉厨,確定程序語(yǔ)義是合法的锌奴、符合邏輯的兽狭。在元數(shù)據(jù)驗(yàn)證階段對(duì)數(shù)據(jù)類(lèi)型做出驗(yàn)證后憾股,這個(gè)階段主要對(duì)類(lèi)的方法做出分析,保證類(lèi)的方法在運(yùn)行時(shí)不會(huì)做出威海虛擬機(jī)安全的事箕慧。
(4)符號(hào)引用驗(yàn)證:它是驗(yàn)證的最后一個(gè)階段服球,發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候。主要是對(duì)類(lèi)自身以外的信息進(jìn)行校驗(yàn)颠焦。目的是確保解析動(dòng)作能夠完成斩熊。
對(duì)整個(gè)類(lèi)加載機(jī)制而言,驗(yàn)證階段是一個(gè)很重要但是非必需的階段伐庭,如果我們的代碼能夠確保沒(méi)有問(wèn)題粉渠,那么我們就沒(méi)有必要去驗(yàn)證,畢竟驗(yàn)證需要花費(fèi)一定的的時(shí)間圾另。當(dāng)然我們可以使用-Xverfity:none來(lái)關(guān)閉大部分的驗(yàn)證霸株。
2.2.3 準(zhǔn)備
準(zhǔn)備階段主要為類(lèi)變量分配內(nèi)存并設(shè)置初始值。這些內(nèi)存都在方法區(qū)分配集乔。在這個(gè)階段我們只需要注意兩點(diǎn)就好了去件,也就是類(lèi)變量和初始值兩個(gè)關(guān)鍵詞:
(1)類(lèi)變量(static)會(huì)分配內(nèi)存,但是實(shí)例變量不會(huì)扰路,實(shí)例變量主要隨著對(duì)象的實(shí)例化一塊分配到j(luò)ava堆中尤溜,
(2)這里的初始值指的是數(shù)據(jù)類(lèi)型默認(rèn)值,而不是代碼中被顯示賦予的值汗唱。比如
public static int value = 1; //在這里準(zhǔn)備階段過(guò)后的value值為0宫莱,而不是1。賦值為1的動(dòng)作在初始化階段哩罪。
當(dāng)然還有其他的默認(rèn)值授霸。
注意肥印,在上面value是被static所修飾的準(zhǔn)備階段之后是0,但是如果同時(shí)被final和static修飾準(zhǔn)備階段之后就是1了绝葡。我們可以理解為static final在編譯器就將結(jié)果放入調(diào)用它的類(lèi)的常量池中了深碱。
2.2.4 解析
解析階段主要是虛擬機(jī)將常量池中的符號(hào)引用轉(zhuǎn)化為直接引用的過(guò)程。什么是符號(hào)應(yīng)用和直接引用呢藏畅?
符號(hào)引用:以一組符號(hào)來(lái)描述所引用的目標(biāo)敷硅,可以是任何形式的字面量,只要是能無(wú)歧義的定位到目標(biāo)就好愉阎,就好比在班級(jí)中绞蹦,老師可以用張三來(lái)代表你,也可以用你的學(xué)號(hào)來(lái)代表你榜旦,但無(wú)論任何方式這些都只是一個(gè)代號(hào)(符號(hào))幽七,這個(gè)代號(hào)指向你(符號(hào)引用)
直接引用:直接引用是可以指向目標(biāo)的指針、相對(duì)偏移量或者是一個(gè)能直接或間接定位到目標(biāo)的句柄溅呢。和虛擬機(jī)實(shí)現(xiàn)的內(nèi)存有關(guān)澡屡,不同的虛擬機(jī)直接引用一般不同。
解析動(dòng)作主要針對(duì)類(lèi)或接口咐旧、字段驶鹉、類(lèi)方法、接口方法铣墨、方法類(lèi)型室埋、方法句柄和調(diào)用點(diǎn)限定符7類(lèi)符號(hào)引用進(jìn)行。
2.2.5 初始化
這是類(lèi)加載機(jī)制的最后一步伊约,在這個(gè)階段姚淆,java程序代碼才開(kāi)始真正執(zhí)行。我們知道屡律,在準(zhǔn)備階段已經(jīng)為類(lèi)變量賦過(guò)一次值腌逢。在初始化階端,程序員可以根據(jù)自己的需求來(lái)賦值了疹尾。一句話描述這個(gè)階段就是執(zhí)行類(lèi)構(gòu)造器< clinit >()方法的過(guò)程上忍。
在初始化階段,主要為類(lèi)的靜態(tài)變量賦予正確的初始值纳本,JVM負(fù)責(zé)對(duì)類(lèi)進(jìn)行初始化窍蓝,主要對(duì)類(lèi)變量進(jìn)行初始化。在Java中對(duì)類(lèi)變量進(jìn)行初始值設(shè)定有兩種方式:
①聲明類(lèi)變量是指定初始值
②使用靜態(tài)代碼塊為類(lèi)變量指定初始值
JVM初始化步驟
1繁成、假如這個(gè)類(lèi)還沒(méi)有被加載和連接吓笙,則程序先加載并連接該類(lèi)
2、假如該類(lèi)的直接父類(lèi)還沒(méi)有被初始化巾腕,則先初始化其直接父類(lèi)
3面睛、假如類(lèi)中有初始化語(yǔ)句絮蒿,則系統(tǒng)依次執(zhí)行這些初始化語(yǔ)句
類(lèi)初始化時(shí)機(jī):只有當(dāng)對(duì)類(lèi)的主動(dòng)使用的時(shí)候才會(huì)導(dǎo)致類(lèi)的初始化,類(lèi)的主動(dòng)使用包括以下六種:
創(chuàng)建類(lèi)的實(shí)例叁鉴,也就是new的方式
訪問(wèn)某個(gè)類(lèi)或接口的靜態(tài)變量土涝,或者對(duì)該靜態(tài)變量賦值
調(diào)用類(lèi)的靜態(tài)方法
反射(如 Class.forName(“com.shengsiyuan.Test”))
初始化某個(gè)類(lèi)的子類(lèi),則其父類(lèi)也會(huì)被初始化
Java虛擬機(jī)啟動(dòng)時(shí)被標(biāo)明為啟動(dòng)類(lèi)的類(lèi)( JavaTest)幌墓,直接使用 java.exe命令來(lái)運(yùn)行某個(gè)主類(lèi)
好了但壮,到目前為止就是類(lèi)加載機(jī)制的整個(gè)過(guò)程,但是還有一個(gè)重要的概念常侣,那就是類(lèi)加載器蜡饵。在加載階段其實(shí)我們提到過(guò)類(lèi)加載器,說(shuō)是在后面詳細(xì)說(shuō)胳施,在這就好好地介紹一下類(lèi)加載器溯祸。