×

java虛擬機(jī)類加載機(jī)制

  • 作者:新網(wǎng)
  • 來源:新網(wǎng)
  • 瀏覽:100
  • 2018-05-02 17:54:06

虛擬機(jī)把類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析、初始化,最后形成可以被虛擬機(jī)直接使用的Java類型。這就是Java虛擬機(jī)的類加載機(jī)制。

   虛擬機(jī)把類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析、初始化,最后形成可以被虛擬機(jī)直接使用的Java類型。這就是Java虛擬機(jī)的類加載機(jī)制。

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

免責(zé)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn)自行上傳,本網(wǎng)站不擁有所有權(quán),也不承認(rèn)相關(guān)法律責(zé)任。如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,請發(fā)送郵件至:operations@xinnet.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),本站將立刻刪除涉嫌侵權(quán)內(nèi)容。

免費(fèi)咨詢獲取折扣

Loading