jdk是一套java開發(fā)工具,其中包括了java運行時環(huán)境jre没龙,在jre中有jvm(java虛擬機)铺厨,代碼運行在jvm中
如下圖所示,JVM類加載機制分為五個部分:加載硬纤,驗證解滓,準備,解析筝家,初始化洼裤,下面我們就分別來看一下這五個過程。
1.概述
???????Class文件由類裝載器裝載后溪王,在JVM中將形成一份描述Class結(jié)構(gòu)的元信息對象腮鞍,通過該元信息對象可以獲知Class的結(jié)構(gòu)信息:如構(gòu)造函數(shù),屬性和方法等莹菱,Java允許用戶借由這個Class相關(guān)的元信息對象間接調(diào)用Class對象的功能移国。
????????虛擬機把描述類的數(shù)據(jù)從class文件加載到內(nèi)存,并對數(shù)據(jù)進行校驗道伟,轉(zhuǎn)換解析和初始化迹缀,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制
2.工作機制
類裝載器就是尋找類的字節(jié)碼文件蜜徽,并構(gòu)造出類在JVM內(nèi)部表示的對象組件祝懂。在Java中,類裝載器把一個類裝入JVM中拘鞋,要經(jīng)過以下步驟:
(1)?裝載:查找和導(dǎo)入Class文件砚蓬;
(2)?鏈接:把類的二進制數(shù)據(jù)合并到JRE中;
?(a)校驗:檢查載入Class文件數(shù)據(jù)的正確性掐禁;
?(b)準備:給類的靜態(tài)變量分配存儲空間怜械;
?(c)解析:將符號引用轉(zhuǎn)成直接引用;
(3)?初始化:對類的靜態(tài)變量傅事,靜態(tài)代碼塊執(zhí)行初始化操作
【裝載】
?在裝載階段缕允,虛擬機需要完成以下3件事情
????????(1)?通過一個類的全限定名來獲取定義此類的二進制字節(jié)流
????????(2)?將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
????????(3)?在Java堆中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這些數(shù)據(jù)的訪問入口蹭越。
虛擬機規(guī)范中并沒有準確說明二進制字節(jié)流應(yīng)該從哪里獲取以及怎樣獲取,這里可以通過定義自己的類加載器去控制字節(jié)流的獲取方式障本。注意這里不一定非得要從一個Class文件獲取,這里既可以從ZIP包中讀取(比如從jar包和war包中讀燃菟)案训,也可以在運行時計算生成(動態(tài)代理),也可以由其它文件生成(比如將JSP文件轉(zhuǎn)換成對應(yīng)的Class類)粪糙。
【驗證】
????虛擬機如果不檢查輸入的字節(jié)流强霎,對其完全信任的話,很可能會因為載入了有害的字節(jié)流而導(dǎo)致系統(tǒng)奔潰蓉冈。
【準備】
準備階段是正式為類變量分配內(nèi)存并設(shè)置類變量的初始值階段城舞,即在方法區(qū)中分配這些變量所使用的內(nèi)存空間。注意這里所說的初始值概念寞酿,比如一個類變量定義為:
public static int v = 8080;
實際上變量v在準備階段過后的初始值為0而不是8080家夺,將v賦值為8080的putstatic指令是程序被編譯后,存放于類構(gòu)造器方法之中伐弹,這里我們后面會解釋拉馋。
但是注意如果聲明為:
public static final int v = 8080;
在編譯階段會為v生成ConstantValue屬性,在準備階段虛擬機會根據(jù)ConstantValue屬性將v賦值為8080惨好。
【解析】
解析階段是指虛擬機將常量池中的符號引用替換為直接引用的過程煌茴。符號引用就是class文件中的:
CONSTANT_Class_info
CONSTANT_Field_info
CONSTANT_Method_info
等類型的常量。
下面我們解釋一下符號引用和直接引用的概念:
符號引用與虛擬機實現(xiàn)的布局無關(guān)昧狮,引用的目標并不一定要已經(jīng)加載到內(nèi)存中景馁。各種虛擬機實現(xiàn)的內(nèi)存布局可以各不相同,但是它們能接受的符號引用必須是一致的逗鸣,因為符號引用的字面量形式明確定義在Java虛擬機規(guī)范的Class文件格式中。
直接引用可以是指向目標的指針绰精,相對偏移量或是一個能間接定位到目標的句柄撒璧。如果有了直接引用,那引用的目標必定已經(jīng)在內(nèi)存中存在笨使。
【類初始化】
??????(1)?遇到new卿樱、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時硫椰,如果類沒有進行過初始化繁调,則需要先觸發(fā)其初始化。生成這4條指令的最常見的Java代碼場景是:使用new關(guān)鍵字實例化對象的時候靶草,讀取或設(shè)置一個類的靜態(tài)字段(被final修飾蹄胰、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時候,以及調(diào)用一個類的靜態(tài)方法的時候奕翔。
??????(2)?使用java.lang.reflect包的方法對類進行反射調(diào)用的時候裕寨,如果類沒有進行過初始化,則需要先觸發(fā)其初始化。
??????(3)?當初始化一個類的時候宾袜,如果發(fā)現(xiàn)其父類還沒有進行過初始化捻艳,則需要先觸發(fā)其父類的初始化。
? ? ? (4)當虛擬機啟動時庆猫,用戶需要指定一個要執(zhí)行的主類(包含main()方法的那個類)认轨,虛擬機會先初始化這個主類。
只有上述四種情況會觸發(fā)初始化月培,也稱為對一個類進行主動引用嘁字,除此以外,所有其他方式都不會觸發(fā)初始化节视,稱為被動引用
代碼清單1
上述代碼運行后拳锚,只會輸出【---SuperClass?init】,?而不會輸出【SubClass?init】,對于靜態(tài)字段,只有直接定義這個字段的類才會被初始化,因此寻行,通過子類來調(diào)用父類的靜態(tài)字段霍掺,只會觸發(fā)父類的初始化,但是這是要看不同的虛擬機的不同實現(xiàn)。
代碼清單2
此處不會引起SuperClass的初始化拌蜘,但是卻觸發(fā)了【[Ltest.SuperClass】的初始化杆烁,通過arr.toString()可以看出,對于用戶代碼來說简卧,這不是一個合法的類名稱兔魂,它是由虛擬機自動生成的,直接繼承于Object的子類举娩,創(chuàng)建動作由字節(jié)碼指令newarray觸發(fā),此時數(shù)組越界檢查也會伴隨數(shù)組對象的所有調(diào)用過程析校,越界檢查并不是封裝在數(shù)組元素訪問的類中,而是封裝在數(shù)組訪問的xaload,xastore字節(jié)碼指令中.
代碼清單3
對常量ConstClass.value?的引用實際都被轉(zhuǎn)化為NotInitialization類對自身常量池的引用铜涉,這兩個類被編譯成class后不存在任何聯(lián)系智玻。
示例題:類加載器與類的”相同“判斷
類加載器除了用于加載類外,還可用于確定類在Java虛擬機中的唯一性芙代。
即便是同樣的字節(jié)代碼吊奢,被不同的類加載器加載之后所得到的類,也是不同的纹烹。
通俗一點來講页滚,要判斷兩個類是否“相同”,前提是這兩個類必須被同一個類加載器加載铺呵,否則這個兩個類不“相同”裹驰。
這里指的“相同”,包括類的Class對象的equals()方法陪蜻、isAssignableFrom()方法邦马、isInstance()方法、instanceof關(guān)鍵字等判斷出來的結(jié)果。