虛擬機把Class文件(描述類的數(shù)據(jù))加載到內(nèi)存沙咏,并對數(shù)據(jù)進行校驗、轉(zhuǎn)換解析和初始化班套,最終形成可以被虛擬機直接使用的Java類型肢藐,這就是類加載機制。
Java中吱韭,類顯性的加載吆豹、連接、初始化都是在 運行期 完成理盆。
Class文件是一串二進制的字節(jié)流痘煤。
1、類加載總體流程圖
類的整個生命周期分為以上七個階段:加載猿规、驗證衷快、準(zhǔn)備、解析姨俩、初始化蘸拔、使用、卸載环葵。
類加載的全過程包括5個階段:加載调窍、驗證、準(zhǔn)備张遭、解析邓萨、初始化。
2、虛擬機規(guī)范規(guī)定了如下幾種情況就必須要進行初始化
遇到new先誉、getstatic湿刽、putstatic、invokestatic指令時褐耳,對應(yīng)到程序中就是使用到new實例化對象時诈闺、讀取或設(shè)置類靜態(tài)字段時(非final)、調(diào)用靜態(tài)方法時铃芦。需要進行初始化雅镊。
使用java.lang.reflect包的方法對類進行反射調(diào)用時,需要進行初始化刃滓。
使用一個類時仁烹,若其父類還未初始化,則需先初始化其父類咧虎。
虛擬機啟動時卓缰,包含main方法的類(主類),虛擬機會將其初始化砰诵。
java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getStatic征唬、REF_putStatic、REF_invokeStatic方法句柄茁彭,并且這個方法句柄對應(yīng)的類沒有進行初始化总寒,則需要先進行初始化。
以上五種情況稱為主動使用理肺,其他的情況均稱為被動使用摄闸,被動使用不會導(dǎo)致初始化。
3妹萨、類加載的全過程
類加載的全過程包括5個階段:加載年枕、驗證、準(zhǔn)備乎完、解析画切、初始化。
3.1 加載
加載階段虛擬機需要完成如下事情:
通過類的全限定名來獲取此類的二進制字節(jié)流囱怕。
將二進制字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)。
在內(nèi)存中生成一個代表這個類的java.lang.Class對象毫别,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口娃弓。
對于 數(shù)組 類而言,數(shù)組類由java虛擬機直接創(chuàng)建岛宦,不通過類加載器創(chuàng)建台丛。數(shù)組類的創(chuàng)建過程如下:
如果數(shù)組元素類型是引用類型,就采用雙親委派模型進行加載(之后會介紹),數(shù)組類將在加載該元素類型的類名稱空間上被標(biāo)識挽霉。
如果數(shù)組元素類型為基本類型防嗡,數(shù)組類被標(biāo)記為與引導(dǎo)類加載器關(guān)聯(lián)。
數(shù)組類的可見性與其元素類型可見性一致侠坎,如果元素類型不是引用類型蚁趁,那數(shù)組類的可見性默認(rèn)為public。
創(chuàng)建的Class對象存放在方法區(qū)中实胸。對象絕大多數(shù)放在堆中他嫡,Class對象是一個例外。
3.2 驗證
此階段是確保class文件的字節(jié)流包含的信息符合虛擬機的要求庐完。主要會進行如下的驗證钢属。
- 文件格式驗證
驗證字節(jié)流是否符合Class文件格式規(guī)范。如是否以魔數(shù)開頭门躯、主次版本號是否能被虛擬機處理淆党、常量池的常量中是否有不被支持的常量類型等等。
- 元數(shù)據(jù)驗證
對字節(jié)碼描述的信息進行語義分析讶凉。如這個類是否有父類染乌、這個類是否繼承了不允許被繼承的類、非抽象類是否實現(xiàn)了父類或接口中要求實現(xiàn)的所有方法等等缀遍。
- 字節(jié)碼驗證
分析數(shù)據(jù)流和控制流慕匠,確定程序語義是否合法,符合邏輯域醇。如任意時刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作台谊、保證跳轉(zhuǎn)指令不會跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上等等。
- 符號引用驗證
符號引用轉(zhuǎn)化為直接引用的時候進行驗證譬挚。如符號引用中通過字符串描述的全限定名是否能夠找到對應(yīng)的類锅铅、指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段等等。
3.3 準(zhǔn)備
為類變量(被static修飾的變量)分配內(nèi)存并為類變量設(shè)置系統(tǒng)初始值的階段减宣,這些類變量所使用的內(nèi)存都將在方法區(qū)進行分配盐须。靜態(tài)字段(非final)會被賦予系統(tǒng)默認(rèn)值,而對于常量字段(final漆腌、static)贼邓,在準(zhǔn)備階段直接賦予用戶設(shè)定的值。
3.4 解析
將 常量池 中的 符號引用 替換為 直接引用 的過程闷尿。
符號引用:符號引用以一組無歧義的符號來描述所引用的目標(biāo)塑径。符號引用與虛擬機的內(nèi)存布局無關(guān),引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中填具。
直接引用:直接引用可以是直接指向目標(biāo)的指針统舀、相對偏移量或是一個能夠間接定位到目標(biāo)的句柄。直接引用和虛擬機實現(xiàn)的內(nèi)存布局有關(guān),引用的目標(biāo)必須已經(jīng)存在于內(nèi)存中誉简。
解析動作主要是針對7中常量類型:
CONSTANT_Class_info
CONSTANT_Fieldref_info
CONSTANT_Methodref_info
CONSTANT_InterfaceMethodref_info
CONSTANT_MethodType_info
CONSTANT_MethodHandle_info
CONSTANT_InvokeDynamic_info
3.5 初始化
在此階段碉就,才開始真正執(zhí)行用戶自定的java代碼。在準(zhǔn)備階段闷串,類變量已經(jīng)被賦予了系統(tǒng)默認(rèn)值瓮钥,而在初始化階段,會賦予用戶自定義的值窿克。
初始化階段就是執(zhí)行類構(gòu)造器 <clinit>() 方法的過程骏庸。
<clinit>() 方法
由編譯器收集類中的所有類變量的賦值動作(如果僅僅只是聲明,不會被收集)和靜態(tài)語句塊中的語句合并產(chǎn)生的年叮,收集順序按照語句在源文件中出現(xiàn)的順序所決定具被;在靜態(tài)語句塊中只能訪問定義在靜態(tài)語句之前的變量;而對于定義在靜態(tài)語句塊之后的變量一姿,在前面的靜態(tài)語句塊可以對它進行賦值,但是不能夠訪問跃惫。
不需要顯示調(diào)用父類構(gòu)造器叮叹。虛擬機會保證在子類的<clinit>()方法執(zhí)行之前,父類的<clinit>()方法已經(jīng)執(zhí)行完畢爆存,所以蛉顽,第一個被執(zhí)行的<clinit>()方法的類肯定是java.lang.Object。
父類中定義的靜態(tài)語句塊優(yōu)先于子類的靜態(tài)語句先较。
<clinit>()方法對類和接口都不是必須的携冤,若類中沒有靜態(tài)語句塊和靜態(tài)變量賦值操作,則不會生成<clinit>()方法闲勺。
接口中不能使用靜態(tài)語句塊曾棕,但任然有變量初始化的賦值操作,因此接口也會生成<clinit>()方法菜循。不同的是翘地,執(zhí)行接口的<clinit>()方法不需要先執(zhí)行父接口的<clinit>()方法,只有在使用了父接口的變量時癌幕,父接口才會進行初始化衙耕。接口的實現(xiàn)類在初始化時也不會執(zhí)行接口的<clinit>()方法。
虛擬機會保證<clinit>()方法在多線程環(huán)境中會被正確的加鎖勺远、同步臭杰。