前言
本篇主要用來(lái)記錄JAVA類加載過(guò)程以及對(duì)象的創(chuàng)建過(guò)程坯屿。闡述他們之間的關(guān)系以及自己學(xué)習(xí)過(guò)程中的疑惑巍扛。
1. 類加載過(guò)程
類加載過(guò)程是指jvm把通過(guò).java文件編譯生成的.class文件中的類信息加載 進(jìn)內(nèi)存的過(guò)程。
類加載過(guò)程可分為加載撤奸、鏈接和初始化。初始化又可以被細(xì)分為驗(yàn)證矢棚、準(zhǔn)備和解析。
加載
類加載器可分為啟動(dòng)類加載器(Bootstrap ClassLoader)蒲肋、擴(kuò)張類加載器(Extension ClassLoader)和應(yīng)用類加載器(Application ClassLoader)劫拢。他們之間的繼承關(guān)系如下圖所示。
類加載器會(huì)動(dòng)態(tài)的加載java類到虛擬機(jī)內(nèi)存中妹沙,并且會(huì)按需加載熟吏,只有在需要時(shí)才對(duì)java類進(jìn)行加載,并且每個(gè)類只會(huì)被加載一次牵寺。
類加載器在加載Java類時(shí)會(huì)采用雙親委派模型:當(dāng)類加載器在加載java類時(shí),會(huì)先委派給父類加載趣斤,層層上傳黎休,最后會(huì)把加載任務(wù)委派給啟動(dòng)類加載器浓领。只有父類類加載器無(wú)法完成這個(gè)加載任務(wù)時(shí)(搜索范圍中沒有找到對(duì)應(yīng)的類),子加載器才會(huì)嘗試自己去加載漫仆。 所以是一個(gè)向上傳遞再向下傳遞的過(guò)程泪幌。
驗(yàn)證
驗(yàn)證階段需要驗(yàn)證四部分內(nèi)容:
- 文件格式驗(yàn)證: 包括文件頭部的魔術(shù)因子、class文件主次版本號(hào)祸泪、class文件的MD5指紋、變量類型是否支持等
- 元數(shù)據(jù)驗(yàn)證: 驗(yàn)證加載的類是否有父類拓萌,父類是否被final修飾(修飾不能被繼承),如果父類是否為抽象類等微王,如果是需要實(shí)現(xiàn)抽象的方法品嚣。
- 字節(jié)碼驗(yàn)證: 來(lái)確保被加載的類方體不會(huì)威脅到虛擬機(jī)。
- 符號(hào)引用驗(yàn)證: 驗(yàn)證符號(hào)引用能否轉(zhuǎn)換為直接引用翰撑, 保證解析動(dòng)作的順利執(zhí)行 。
準(zhǔn)備
public static int classValue = 10;
類變量classValue被附上默認(rèn)初值0涨醋。 比如8種基本類型的初值逝撬,默認(rèn)為0浴骂;引用類型的初值則為null宪潮;常量的初值即為代碼中設(shè)置的值。
解析
解析就是在常量池中尋找類梯轻、接口尽棕、字段和方法的符號(hào)引用,并且將這些符號(hào)引用替換成直接引用的過(guò)程。
初始化
為所有靜態(tài)變量賦代碼中指定的值媚朦。類變量classValue被附上初值10。
2. 對(duì)象創(chuàng)建過(guò)程
(1)類加載檢查:首先檢查new指令的參數(shù)能否在常量池中定位到一個(gè)類的符號(hào)引用。這個(gè)符號(hào)引用代表的類是否已被加載孙乖、解析和初始化過(guò)。如果沒有弯屈,那必須先執(zhí)行相應(yīng)的類加載過(guò)程恋拷。
(2)分配內(nèi)存:虛擬機(jī)為新生的對(duì)象分配內(nèi)存,對(duì)象所需的內(nèi)存大小在類加載完成后便可完全確定蔬顾。
(3)初始化:內(nèi)存分配完成后,虛擬機(jī)會(huì)把分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭)窄刘,保證了對(duì)象實(shí)例字段在Java代碼中可以不賦初值就可以直接使用舷胜。
(4)設(shè)置對(duì)象頭:虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置娩践,例如這個(gè)對(duì)象是哪個(gè)類的實(shí)例烹骨、如何才能找到類的元數(shù)據(jù)信息、對(duì)象的哈希碼吨岭、對(duì)象的GC分代年齡等信息峦树。這些信息存放在對(duì)象的對(duì)象頭中未妹。
(5)執(zhí)行<init>方法:把對(duì)象按照程序員的意愿進(jìn)行初始化空入。
疑問(wèn)
為什么類加載器要實(shí)現(xiàn)雙親委派模型?
- 避免類的重復(fù)加載:父類如果已經(jīng)加載過(guò)化戳,子類就不用重復(fù)加載。
- 消除安全隱患:如果不使用雙親委派模型点楼,比如用戶可以任意實(shí)現(xiàn)自己的String類,并用引用類加載器加載换怖。那么自定義的String類就會(huì)和java核心庫(kù)中的String類沖突 蟀瞧,這樣回導(dǎo)致非常大的安全隱患沉颂。
如何打破雙親委派模型悦污?
默認(rèn)的loadClass方法是實(shí)現(xiàn)了雙親委派機(jī)制的邏輯,即會(huì)先讓父類加載器加載彻坛,當(dāng)無(wú)法加載時(shí)才回調(diào)用findClass方法自己加載踏枣。 如果想打破雙親委派模型,那么就重寫整個(gè)loadClass方法茵瀑。如果不想打破雙親委派模型,只需要重寫findClass方法即可瘾婿。
為什么類加載過(guò)程和對(duì)象創(chuàng)建過(guò)程都存在初始化零值?
類加載過(guò)程在準(zhǔn)備階段的初始化零值時(shí)針對(duì)類變量抢呆,對(duì)象創(chuàng)建過(guò)程中的初始化針對(duì)的時(shí)實(shí)例變量笛谦。類變量屬于類,實(shí)例變量屬于對(duì)象饥脑,實(shí)例變量生命周期和對(duì)象的生命周期相同。