ClassLoader主要職責(zé)就是加載各種class文件到JVM中匆骗,他是一個(gè)抽象的類,給定一個(gè)class的二進(jìn)制文件名椒涯,他會(huì)嘗試加載并且在Jvm中生成構(gòu)成這個(gè)類的各個(gè)數(shù)據(jù)結(jié)構(gòu)柄沮,然后使其分布在Jvm對(duì)應(yīng)的內(nèi)存區(qū)域中。
類的加載過(guò)程分為三個(gè)階段废岂,加載階段祖搓,連接階段,和初始化階段
加載階段
主要負(fù)責(zé)查找并且加載類的二進(jìn)制文件湖苞,其實(shí)就是class文件拯欧,將class文件中的二進(jìn)制數(shù)據(jù)讀取到內(nèi)存中,然后將該字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)中運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu)财骨,并且在堆中生成一個(gè)該類的class對(duì)象作為訪問(wèn)方法區(qū)數(shù)據(jù)結(jié)構(gòu)的入口
棧內(nèi)存:ClassLoader的引用镐作,Aclass對(duì)象的引用藏姐,A對(duì)象的引用
堆內(nèi)存:ClassLoader對(duì)象,A的class對(duì)象该贾,A對(duì)象
方法區(qū):A類的數(shù)據(jù)結(jié)構(gòu)
不管某個(gè)類被加載了多少次羔杨,對(duì)應(yīng)到堆內(nèi)存中的class對(duì)象始終是同一個(gè),他是類加載的最終產(chǎn)物杨蛋。
連接階段分為三個(gè)部分:驗(yàn)證兜材、準(zhǔn)備、解析
驗(yàn)證:
1.驗(yàn)證文件格式
- 二進(jìn)制文件頭部的魔術(shù)因子逞力,該因子決定了文件是什么類型
- 主次版本號(hào)曙寡,高版本的Jvm編譯的class不能被低版本的Jvm所兼容
- class文件的字節(jié)流是否殘缺(每個(gè)類在編譯的時(shí)候會(huì)經(jīng)過(guò)md5摘要算法之后會(huì)作為字節(jié)流的一部分)
- 常量池中的常量是否存在不被支持的變量類型
- 指向常量的引用是否指到了不存在的常量或者該常量的類型不被支持
- 元數(shù)據(jù)的驗(yàn)證(對(duì)class的字節(jié)流進(jìn)行語(yǔ)義分析,確保符合Jvm規(guī)范)
- 檢查這個(gè)類是否存在父類寇荧,是否繼承了某個(gè)接口举庶,這些父類和接口是否合法或者存在
-檢查該類是否繼承了被final修飾的類,被final修飾的類是不允許被繼承的砚亭,并且其中的方法是不允許被覆蓋的 - 檢查該類是否是抽象類灯变,如果不是,是否實(shí)現(xiàn)了父類的抽象接口中的所有方法
- 檢查方法重載的合法性捅膘,比如相同的方法名稱添祸,參數(shù)但是返回類型不相同是不被允許的
- 字節(jié)碼的驗(yàn)證
- 保證當(dāng)前線程在程序計(jì)數(shù)器中的指令不會(huì)跳轉(zhuǎn)到不合法的字節(jié)碼指令
- 保證類型的轉(zhuǎn)換是否合法,比如用A聲明的引用寻仗,不能用B進(jìn)行強(qiáng)制轉(zhuǎn)換
-保證任意時(shí)刻刃泌,虛擬機(jī)棧中的操作棧類型與指令代碼都能正確的被執(zhí)行,比如壓棧的時(shí)候傳入的是A類型的引用署尤,在使用的時(shí)候卻將B類型載入了本地變量表
- 符號(hào)引用驗(yàn)證(主要是驗(yàn)證某個(gè)類的字段不存在耙替,或者方法不存在拋出異常,反射的時(shí)候比較多見(jiàn))
- 通過(guò)符號(hào)引用描述的字符串全限定名稱是否能夠順利的找到相關(guān)的類
- 符號(hào)引用中的類曹体,方法俗扇,字段,是否對(duì)當(dāng)前類可見(jiàn)箕别,比如不能訪問(wèn)引用類的私有方法
5.其他驗(yàn)證
準(zhǔn)備:
當(dāng)一個(gè)class的字節(jié)流通過(guò)了所有的準(zhǔn)備驗(yàn)證過(guò)程之后铜幽,就開(kāi)始為該對(duì)象的類變量(靜態(tài)變量)分配內(nèi)存且設(shè)置初始值,類變量的內(nèi)存會(huì)被分配到方法區(qū)中串稀,不同于實(shí)例變量會(huì)被分配到堆內(nèi)存中除抛,所謂分配初始值,就是為相應(yīng)的類變量給定一個(gè)相關(guān)類型在沒(méi)有被設(shè)置值時(shí)的默認(rèn)值
public static int a=10,在連接階段初始值是0,
public final static int b=10,不會(huì)導(dǎo)致類的初始化母截,是一種被動(dòng)引用到忽,他不存在連接階段,他在編譯階段就會(huì)被直接賦值為10
解析:
Simple simple=new Simple();
System.out.println(simple.name());
如上 我們可以通過(guò)simple訪問(wèn)Simple中的可見(jiàn)屬性和方法清寇,但是在字節(jié)碼中喘漏,他會(huì)被編譯成相應(yīng)的助記符(符號(hào)引用)护蝶,在類的解析過(guò)程中助記符還需要進(jìn)一步解析才能正確的找到所對(duì)應(yīng)的堆內(nèi)存中的Simple數(shù)據(jù)結(jié)構(gòu)。
- 字段解析
在解析類或者變量的時(shí)候比如上述Simple在調(diào)用他的屬性和方法的時(shí)候陷遮,如果該字段不存在滓走,或者出現(xiàn)錯(cuò)誤就會(huì)拋出異常
如果Simple中本身就包含這個(gè)字段,則直接返回這個(gè)字段的引用帽馋,當(dāng)然也要對(duì)該字段所屬的類提前進(jìn)行類加載
如果Simple中沒(méi)有該字段,則會(huì)根據(jù)繼承關(guān)系自下而上比吭,查找父類或者接口的字段绽族,找到即可返回,同樣也需要提前對(duì)找到的字段進(jìn)行類的加載過(guò)程
如果Simple中沒(méi)有字段衩藤,一直找到了Object還是沒(méi)有吧慢,則表示查找失敗,直接拋出異常
這就解釋了子類為什么重寫(xiě)了父類的字段之后能夠生效的原因赏表,因?yàn)檎业阶宇惖淖侄尉椭苯映跏蓟祷亓?/li> - 類方法的解析
類方法和接口不同检诗,類方法可以直接使用該類調(diào)用,接口方法必須要有相應(yīng)的實(shí)現(xiàn)類繼承才能夠進(jìn)行調(diào)用
若在類方法表中發(fā)現(xiàn)索引的Simple是一個(gè)接口不是一個(gè)類瓢剿,則直接返回錯(cuò)誤
在Simple中查找是否有目標(biāo)方法逢慌,如果有直接返回該方法的引用,否則繼續(xù)向上查找
如果父類中仍然沒(méi)有找到則拋出異常
如果在當(dāng)前類或者父類中找到了目標(biāo)方法间狂,但是它是一個(gè)抽象類攻泼,則也會(huì)拋出異常 - 接口方法的解析
接口不僅可以定義方法,還可以繼承其他接口
如果在接口方法表中發(fā)現(xiàn)索引的simple是一個(gè)類而不是接口鉴象,則會(huì)直接返回錯(cuò)誤忙菠,因?yàn)榉椒ń涌诒砗皖惤涌诒硭菁{的類型應(yīng)該是不一樣的
接下來(lái)的查找就和類方法的解析比較類似了,自下而上查找纺弊,直到找到為止牛欢,如果沒(méi)有拋出異常 - 類接口的解析
如果simple是一個(gè)數(shù)組類型,則虛擬機(jī)不需要完成對(duì)simple的加載淆游,只需要在虛擬機(jī)中生成一個(gè)能夠代表該類型的對(duì)象傍睹,并且在堆內(nèi)存中開(kāi)辟一片連續(xù)的地址空間即可
初始化階段主要為類的靜態(tài)變量賦予正確的初始值(代碼編寫(xiě)階段給定的值)
每個(gè)類或者接口被java程序首次主動(dòng)使用時(shí),JVM才會(huì)對(duì)其進(jìn)行初始化,所以程序包中那么多類并不是一次性全部被JVM初始化的稽犁,他很lazy.
靜態(tài)語(yǔ)句塊只能對(duì)后面的靜態(tài)變量賦值焰望,不能訪問(wèn),否則無(wú)法通過(guò)編譯
static{
System.out.println(x);
x=100;
}
private static int x=10;
虛擬機(jī)會(huì)保證父類的<clinit>方法最先執(zhí)行,因此父類的靜態(tài)變量總是能夠得到優(yōu)先賦值
主動(dòng)使用的場(chǎng)景如下:
1.通過(guò)new關(guān)鍵字(會(huì)導(dǎo)致類的加載和最終初始化)TestApplication test=new TestApplication();
2.訪問(wèn)類的靜態(tài)變量,讀取和更新都會(huì)導(dǎo)致類的初始化 public static int x=10;但是已亥,要記住熊赖,引用類的靜態(tài)常量不會(huì)導(dǎo)致類被初始化,如 public final static int x=10;他是一個(gè)常量虑椎,不過(guò)需要計(jì)算的常量如:public final static int RANDOM=new Random().nextInt()例外震鹉,由于他在類的加載和和連接階段無(wú)法對(duì)其進(jìn)行計(jì)算俱笛,需要進(jìn)行初始化后才能對(duì)其賦值。
3.訪問(wèn)類的靜態(tài)方法也會(huì)導(dǎo)致類的初始化
4.對(duì)某個(gè)類進(jìn)行反射操作會(huì)導(dǎo)致類的初始化 Class.forName("packageName+ClassName");
5.初始化子類會(huì)導(dǎo)致父類也被初始化传趾,比如訪問(wèn)子類的靜態(tài)變量或者靜態(tài)方法迎膜,或者new一個(gè)子類,都會(huì)導(dǎo)致父類也被初始化浆兰,但是通過(guò)子類使用父類的靜態(tài)變量或者靜態(tài)方法只會(huì)導(dǎo)致父類被初始化磕仅,子類不會(huì)被初始化