Java虛擬機類加載機制

1.概述

虛擬機把類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析、初始化,最後形成可以被虛擬機直接使用的Java類型。這就是Java虛擬機的類加載機制。

與編譯時需要連接的語言不同,Java語言類加載、連接和初始化都是在程序執行期間完成的,增加了性能開銷但提高了就Java程序的靈活性,Java裡天生可以動態擴展的語言特性就依賴運行期動態加載和動態鏈接這個特點完成的。

2.類加載的時機

(1)類從加載到虛擬機內存,到卸載出內存,整個生命週期:加載、驗證、準備、解析、初始化、使用、卸載。其中驗證、準備、解析統稱連接。

Java虛擬機類加載機制

(2)加載、驗證、準備、初始化、卸載這5個階段順序是確定的(開始時間順序一定,進行或完成通常是交叉的),解析可能在初始化後再開始,這是為了支持Java語言的運行時綁定。

(3)加載的時機沒有強制約束,初始化卻有,下面5種情況必須進行立即對類的初始化(有且只有這5種)

1.使用new實例化對象、讀取或設置、一個類的靜態字段(final修飾,編譯期把結果放入常量池的靜態字段除外),調用一個類的靜態方法;

2.使用Java.lang.reflect包的方法對類進行反射調用;

3.當初始化一個類時,該類的父類沒有進行初始化,就觸發父類初始化;

4.當虛擬機啟動時,用戶包含main()方法的類,虛擬機會先初始化該類;

5.當使用JDK 1.7的動態語言支持時。

(4)上面5中情況稱為對一個類主動引用,除此之外都是被動引用

被動引用3個例子:

1.通過子類引用父類中定義的靜態字段,只會觸發父類初始化;

2.new一個類的數組,並不會觸發該類初始化,而是初始化一個由虛擬機自動生成,直接繼承Object的子類。該類代表數組元素類型的一維數組,實現了數組應有的屬性和方法,Java語言對數組

的訪問比C/C++相對安全就是因為該類封裝了數組元素的訪問方法。

3.一個類的靜態字段(final修飾,編譯期把結果放入常量池的靜態字段)不會被初始化

(5)一個接口初始化時並不要求其父接口全部完成初始化,只有在真正使用到父接口的時候(如引用接口中定義的常量)才會初始化。

3.類加載的過程

(1)加載

虛擬機完成3件事

1.通過一個類的全限定名獲取定義此類的二進制字節流。

2.將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構

3.生成一個代表該類的java.lang.Class對象,作為方法區這個類的各種數據訪問入口

加載階段是開發人員可控性最強時期,可以使用用戶自定義類加載器完成;數組類本身不能通過類加載器創建,它是由Java虛擬機直接創建,但數組類的元素類型最終還是靠類加載器創建。

加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,然後在內存中實例化一個java.lang.Class對象,並不一定在堆中,HotSpot虛擬機將其存放在方法區。

加載階段與連接階段的部分內容是交叉進行的,加載階段尚未完成,連接階段可能已開始。

(2)驗證

確保Class文件的字節流中包含的信息符合當前虛擬機的要求。Java語言是相對安全的語言,使用純粹的Java代碼無法做到諸如訪問數組邊界以外的數據,將一個對象轉型為它並未實現的類型等等。如果這樣做編譯器就會拒絕編譯,但Class文件並不一定要求是就Java源碼編譯而來,可以是從其他jvm上的語言來或者用十六進制編輯器直接編寫產生Class文件。在字節碼層面上很多Java代碼無法做到的事情是可以實現的,所以需要檢查輸入的字節流,驗證就是虛擬機對自身進行保護的工作。

驗證過程大致完成下面4個階段的檢驗動作:

1.文件格式驗證:

驗證字節流是否符合Class文件格式的規範,並且能被當前版本的虛擬機處理,比如:是否以魔數0xCAFEBABE開頭、主次版本號是否在當前虛擬機處理範圍、常量池中是否有不被支持的常量

類型等等。 只有通過這個階段的驗證後,字節流才會進入內存的方法區進行存儲,所有後面3個階段全部是基於方法區存儲結構進行的,不會直接操作字節流。

2.元數據驗證:

對字節碼描述的信息進行語義分析,以保證其描述的信息符合Java語言規範,比如:是否有父類(除了java.lang.Object之外所有類要有父類)、是的繼承了不該繼承的類(final)、如果不是抽

象類是否實現了其父類或接口中的所有要實現的方法。

3.字節碼原則:

主要目的通過數據流和控制流分析,確定程序語義是合法、符合邏輯的。在第二階段對元信息中的數據類型做完校驗後,這個階段將對類的方法體進行校驗分析,保證被校驗類的方法在運行時

不會做出危害虛擬機安全的事件。比如:保證任意時刻操作數棧的數據類型與指令代碼序列都能配合工作、保證跳轉指令不會跳轉到方法體以外的字節碼等等。

4.符號引用驗證:

這一階段發生在虛擬機將符號引用轉化為直接引用的時候,也就是在解析階段發生。通常需要校驗的內容有:符號引用中通過字符串描述的全限定名是否能找到對應的類、符號引用中類的訪問

性是否能被當前類訪問。

(3)準備

準備階段是正式為類變量分配內存並設置類變量初始化值,這些變量使用的內存都將在方法區中分配,初始化值為零值。注意該階段進行內存分配的僅包括類變量(static),不包括實例變量,實例變量將在對象實例化時隨對象一起分配在堆中。

Java虛擬機類加載機制

(4)解析,將虛擬機將常量池內的符號引用替換為直接引用的過程

(5)初始化,初始化階段真正開始執行類中定義的Java程序代碼,對類變量初始化用戶定義的值

4.類加載器

(1)執行通過一個類的全限定名獲取描述此類的二進制流,該動作放在Java虛擬機外部實現

(2)對於任意一個類,都需要由加載它的類加載期和這個類本身一同確定其在Java虛擬機中的唯一性,每個類加載器都有一個獨立的類名稱空間。

(3)比較兩個類是否相等:Class對象equals()方法、isAssignableFrom()、isInstance()的返回結果,只有在兩個類由同一個類加載器加載的前提下才有意義。

(5)類加載器類型:

1.從Java虛擬機的角度來講,只存在兩種兩種不同的類加載器:啟動類加載器,虛擬機自身的一部分;其他類加載器,獨立於虛擬機外部。

2.從開發人員角度:

啟動類加載器:負責將JAVA_HOME/lib目錄中的,或者被-Xbootclasspath參數指定的路徑中的,並能被虛擬機識別的類庫加載到內存,啟動類加載器無法被Java程序無法直接引用。

擴展類加載器:負責加載JAVA_HOME/lib/ext目錄中,或者被java.ext.dirs系統變量指定的路徑中的所有類庫,開發人員可直接使用。

應用程序類加載器(系統類加載器):負責用戶類路徑(classpath)上指定的類庫,可直接使用,如果應用程序沒有自定義過自己的類加載器一般就用程序默認的類加載器。

(6)雙親委派模型

該模型要求除了頂層的啟動類加載器,其餘的類加載器都應當有自己的父類加載器,父子關係不是通過繼承的關係來實現,而是通過組合關係來複用父加載器。

過程:如果一個類加載器收到類加載的請求,他首先不會自己去嘗試,而是委派父類加載器去完成,每一層都是如此,因此所有的加載請求都會傳到頂層的啟動類加載器,只有當父加載器反饋自己無法加載時,子加載器才嘗試自己加載。

Java虛擬機類加載機制

好處:Java類隨它的類加載器而具有一種優先級的層次關係。比如java.lang.Object無論誰去加載它都會是同一個類(不同類加載器有其獨立的類名稱空間,只有在兩個類由同一個類加載器加載的前提下才有意義),如果各自加載又會有多個Object類,Java類型體系中最基礎的行為也就無法保證。

(7)破壞雙親委派模型

Java不提倡用戶再去覆蓋loadClass()方法,而是把自己的類加載邏輯寫入findClass()方法中,loadClass()邏輯是如果父加載器加載失敗,就調用自己的findClass()方法來完成加載。


分享到:


相關文章: