java虛擬機類加載機制
- 作者:新網
- 來源:新網
- 瀏覽:100
- 2018-05-02 17:54:06
虛擬機把類的數(shù)據(jù)從Class文件加載到內存,并對數(shù)據(jù)進行校驗、轉換解析、初始化,最后形成可以被虛擬機直接使用的Java類型。這就是Java虛擬機的類加載機制。
虛擬機把類的數(shù)據(jù)從Class文件加載到內存,并對數(shù)據(jù)進行校驗、轉換解析、初始化,最后形成可以被虛擬機直接使用的Java類型。這就是Java虛擬機的類加載機制。
<
div> 與編譯時需要連接的語言不同,Java語言類加載、連接和初始化都是在程序執(zhí)行期間完成的,增加了性能開銷但提高了就Java程序的靈活性,Java里天生可以動態(tài)擴展的語言特性就依賴運行期動態(tài)加載和動態(tài)鏈接這個特點完成的。
2.類加載的時機
(1)類從加載到虛擬機內存,到卸載出內存,整個生命周期:加載、驗證、準備、解析、初始化、使用、卸載。其中驗證、準備、解析統(tǒng)稱連接。
(2)加載、驗證、準備、初始化、卸載這5個階段順序是確定的(開始時間順序一定,進行或完成通常是交叉的),解析可能在初始化后再開始,這是為了支持Java語言的運行時綁定。
(3)加載的時機沒有強制約束,初始化卻有,下面5種情況必須進行立即對類的初始化(有且只有這5種)
1.使用new實例化對象、讀取或設置、一個類的靜態(tài)字段(final修飾,編譯期把結果放入常量池的靜態(tài)字段除外),調用一個類的靜態(tài)方法;
2.使用Java
.lang.reflect包的方法對類進行反射調用;
3.當初始化一個類時,該類的父類沒有進行初始化,就觸發(fā)父類初始化;
4.當虛擬機啟動時,用戶包含main()方法的類,虛擬機會先初始化該類;
5.當使用JDK 1.7的動態(tài)語言支持時。
(4)上面5中情況稱為對一個類主動引用,除此之外都是被動引用
被動引用3個例子:
1.通過子類引用父類中定義的靜態(tài)字段,只會觸發(fā)父類初始化;
2.new一個類的數(shù)組,并不會觸發(fā)該類初始化,而是初始化一個由虛擬機自動生成,直接繼承Object的子類。該類代表數(shù)組元素類型的一維數(shù)組,實現(xiàn)了數(shù)組應有的屬性和方法,Java語言對數(shù)組
的訪問比C/C++相對安全就是因為該類封裝了數(shù)組元素的訪問方法。
3.一個類的靜態(tài)字段(final修飾,編譯期把結果放入常量池的靜態(tài)字段)不會被初始化
(5)一個接口初始化時并不要求其父接口全部完成初始化,只有在真正使用到父接口的時候(如引用接口中定義的常量)才會初始化。
3.類加載的過程
(1)加載
虛擬機完成3件事
1.通過一個類的全限定名獲取定義此類的二進制字節(jié)流。
2.將這個字節(jié)流所代表的靜態(tài)存儲結構轉化為方法區(qū)的運行時數(shù)據(jù)結構
3.生成一個代表該類的
java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)訪問入口
加載階段是開發(fā)人員可控性最強時期,可以使用用戶自定義類加載器完成;數(shù)組類本身不能通過類加載器創(chuàng)建,它是由Java虛擬機直接創(chuàng)建,但數(shù)組類的元素類型最終還是靠類加載器創(chuàng)建。
加載階段完成后,虛擬機外部的二進制字節(jié)流就按照虛擬機所需的格式存儲在方法區(qū)之中,然后在內存中實例化一個java.lang.Class對象,并不一定在堆中,HotSpot虛擬機將其存放在方法區(qū)。
加載階段與連接階段的部分內容是交叉進行的,加載階段尚未完成,連接階段可能已開始。
(2)驗證
確保Class文件的字節(jié)流中包含的信息符合當前虛擬機的要求。Java語言是相對安全的語言,使用純粹的Java代碼無法做到諸如訪問數(shù)組邊界以外的數(shù)據(jù),將一個對象轉型為它并未實現(xiàn)的類型等等。如果這樣做編譯器就會拒絕編譯,但Class文件并不一定要求是就Java源碼編譯而來,可以是從其他jvm上的語言來或者用十六進制編輯器直接編寫產生Class文件。在字節(jié)碼層面上很多Java代碼無法做到的事情是可以實現(xiàn)的,所以需要檢查輸入的字節(jié)流,驗證就是虛擬機對自身進行保護的工作。
驗證過程大致完成下面4個階段的檢驗動作:
1.文件格式驗證:
驗證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當前版本的虛擬機處理,比如:是否以魔數(shù)0xCAFEBABE開頭、主次版本號是否在當前虛擬機處理范圍、常量池中是否有不被支持的常量
類型等等。 只有通過這個階段的驗證后,字節(jié)流才會進入內存的方法區(qū)進行存儲,所有后面3個階段全部是基于方法區(qū)存儲結構進行的,不會直接操作字節(jié)流。
2.元數(shù)據(jù)驗證:
對字節(jié)碼描述的信息進行語義分析,以保證其描述的信息符合Java語言規(guī)范,比如:是否有父類(除了java.lang.Object之外所有類要有父類)、是的繼承了不該繼承的類(final)、如果不是抽
象類是否實現(xiàn)了其父類或接口中的所有要實現(xiàn)的方法。
3.字節(jié)碼原則:
主要目的通過數(shù)據(jù)流和控制流分析,確定程序語義是合法、符合邏輯的。在第二階段對元信息中的數(shù)據(jù)類型做完校驗后,這個階段將對類的方法體進行校驗分析,保證被校驗類的方法在運行時
不會做出危害虛擬機安全的事件。比如:保證任意時刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作、保證跳轉指令不會跳轉到方法體以外的字節(jié)碼等等。
4.符號引用驗證:
這一階段發(fā)生在虛擬機將符號引用轉化為直接引用的時候,也就是在解析階段發(fā)生。通常需要校驗的內容有:符號引用中通過字符串描述的全限定名是否能找到對應的類、符號引用中類的訪問
性是否能被當前類訪問。
(3)準備
準備階段是正式為類變量分配內存并設置類變量初始化值,這些變量使用的內存都將在方法區(qū)中分配,初始化值為零值。注意該階段進行內存分配的僅包括類變量(static),不包括實例變量,實例變量將在對象實例化時隨對象一起分配在堆中。
(4)解析,將虛擬機將常量池內的符號引用替換為直接引用的過程
(5)初始化,初始化階段真正開始執(zhí)行類中定義的Java程序代碼,對類變量初始化用戶定義的值
4.類加載器
(1)執(zhí)行通過一個類的全限定名獲取描述此類的二進制流,該動作放在Java虛擬機外部實現(xiàn)
(2)對于任意一個類,都需要由加載它的類加載期和這個類本身一同確定其在Java虛擬機中的唯一性,每個類加載器都有一個獨立的類名稱
空間。
(3)比較兩個類是否相等:Class對象equals()方法、isAssignableFrom()、isInstance()的返回結果,只有在兩個類由同一個類加載器加載的前提下才有意義。
(5)類加載器類型:
1.從Java虛擬機的角度來講,只存在兩種兩種不同的類加載器:啟動類加載器,虛擬機自身的一部分;其他類加載器,獨立于虛擬機外部。
2.從開發(fā)人員角度:
啟動類加載器:負責將JAVA_HOME/lib目錄中的,或者被-Xbootclasspath參數(shù)指定的路徑中的,并能被虛擬機識別的類庫加載到內存,啟動類加載器無法被Java程序無法直接引用。
擴展類加載器:負責加載JAVA_HOME/lib/ext目錄中,或者被java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫,開發(fā)人員可直接使用。
應用程序類加載器(系統(tǒng)類加載器):負責用戶類路徑(classpath)上指定的類庫,可直接使用,如果應用程序沒有自定義過自己的類加載器一般就用程序默認的類加載器。
(6)雙親委派模型
該模型要求除了頂層的啟動類加載器,其余的類加載器都應當有自己的父類加載器,父子關系不是通過繼承的關系來實現(xiàn),而是通過組合關系來復用父加載器。
過程:如果一個類加載器收到類加載的請求,他首先不會自己去嘗試,而是委派父類加載器去完成,每一層都是如此,因此所有的加載請求都會傳到頂層的啟動類加載器,只有當父加載器反饋自己無法加載時,子加載器才嘗試自己加載。
好處:Java類隨它的類加載器而具有一種優(yōu)先級的層次關系。比如java.lang.Object無論誰去加載它都會是同一個類(不同類加載器有其獨立的類名稱空間,只有在兩個類由同一個類加載器加載的前提下才有意義),如果各自加載又會有多個Object類,Java類型體系中最基礎的行為也就無法保證。
(7)破壞雙親委派模型
Java不提倡用戶再去覆蓋loadClass()方法,而是把自己的類加載邏輯寫入findClass()方法中,loadClass()邏輯是如果父加載器加載失敗,就調用自己的findClass()方法來完成加載。
以上就是虛擬機的加載機制。