首頁  >  文章  >  Java  >  Java虛擬機器學習 - 類別載入機制

Java虛擬機器學習 - 類別載入機制

黄舟
黄舟原創
2017-03-18 17:51:551310瀏覽

類別載入機制

JVM把class檔案載入的內存,並對資料進行校驗、轉換解析與初始化,最後形成JVM可以直接使用的Java類型的過程就是載入機制。

類別從被載入到虛擬機器記憶體開始,到卸載出記憶體為止,它的生命週期包括了:載入(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization) 、使用(Using)、卸載(Unloading)七個階段,其中驗證、準備、解析三個部分統稱連結。

加載(裝載)、驗證、準備、初始化和卸載這五個階段順序是固定的,類別的載入過程必須按照這種順序開始,而解析階段不一定;它在某些情況下可以在初始化之後再開始,這是為了運行時動態綁定特性。值得注意的是:這些階段通常都是互相交叉的混合式進行的,通常會在一個階段執行的過程中呼叫或啟動另一個階段。


加載:

加載階段是“類加載機制”中的一個階段,這個階段通常也被稱作“裝載”,主要完成:

1.通過“類全名”來獲取定義此類別的二進位位元組流

2.將位元組流所代表的靜態儲存結構轉換為方法區的執行時間資料結構

3.在java堆中產生一個代表這個類別的java.lang.Class對象,作為方法區這些資料的存取入口

虛擬機規範對於「透過「類別全名」來獲取定義此類的二進位位元組流」並沒有指明二進位流必須要從一個本地class檔案中獲取,準確地說是根本沒有指明要從哪裡獲取及怎樣獲取。例如:

從Zip套件中讀取,這很常見,最終成為日後JAR、EAR、WAR格式的基礎。

從網路獲取,常見應用Applet。

運行時計算生成,這種場景使用的最多的就是動態代理技術,在java.lang.reflect.Proxy中,就是用ProxyGenerator.generateProxyClass來為特定介面產生$Prxoy的代理類別的二進位位元組流。

由其他格式檔案生成,典型場景:JSP應用

從資料庫中讀取,這種場景相對少見,有些中間件伺服器(如SAP Netweaver)可以選擇把程式安裝到資料庫中來完成程式碼在集群間的分發。

相對於類別載入過程的其他階段,載入階段(準備地說,是載入階段中獲取類別的二進位位元組流的動作)是開發期可控性最強的階段,因為載入階段可以使用系統提供的類別載入器(ClassLoader)來完成,也可以由使用者自訂的類別載入器完成,開發人員可以透過定義自己的類別載入器去控製位元組流的取得方式。

載入階段完成後,虛擬機器外部的二進位位元組流就按照虛擬機所需的格式儲存在方法區之中,方法區中的資料儲存格式有虛擬機實作自行定義,虛擬機並未規定此區域的具體資料結構。然後在java堆中實例化一個java.lang.Class類別的對象,這個物件作為程式存取方法區中的這些類型資料的外部介面。加載階段與連結階段的部分內容(如一部分字節碼文​​件格式驗證動作)是交叉進行的,加載階段尚未完成,鏈接階段可能已經開始,但這些夾在加載階段之中進行的動作,仍然屬於鏈接階段的內容,這兩個階段的開始時間仍保持固定的先後順序。


驗證:

驗證是連結階段的第一步,這一步是連結的主要訊息機器的要求,且不會危害虛擬機器本身安全。

驗證階段主要包括四個檢驗過程:檔案格式驗證、元資料驗證、字節碼驗證和符號引用驗證。

1.檔案格式驗證 

 驗證class檔案格式規範,例如: class檔案是否已魔術0xCAFEBABE開頭, 主、次版本號是否在目前虛擬機器處理範圍內等

2.元資料驗證

這個這個階段是對字節碼所描述的資訊進行語意分析,以確保起描述的資訊符合java語言規範要求。驗證點可能包括:這個類別是否有父類別(除了java.lang.Object之外,所有的類別都應當有父類別)、這個類別是否繼承了不允許被繼承的類別(被final修飾的)、如果這個類別的父類別是抽象類別,是否實作了起父類別或介面中要求實作的所有方法。

3.字節碼驗證

 進行資料流和控制流分析,這個階段對類別的方法體進行校驗分析,這個階段的任務是確保被校驗類別的方法在運行時不會做出危害虛擬機器安全的行為。如:確保訪法體中的類型轉換有效,例如可以把一個子類物件賦值給父類資料類型,這是安全的,但不能把一個父類物件賦值給子類資料型別、保證跳轉指令不會跳到方法體以外的字節碼命令上。

4.符號參考驗證

符號引用中透過字串描述的全限定名是否能找到對應的類別、符號引用類別中的類,字段和方法的訪問性(private、protected、public、default)是否可被當前類別存取。


準備:

準備階段是正式為類別變數分配記憶體並設定類別變數初始值的階段,這些記憶體都會在方法區中進行分配。這個階段中有兩個容易產生混淆的知識點,首先是這時候進行內存分配的僅包括類變量(static 修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在java堆中。其次是這裡所說的初始值「通常情況」下是資料型別的零值,假設一個類別變數定義為:

public static int value  = 12;

那麼變數value在準備階段過後的初始值為0而不是12,因為此時尚未開始執行任何java方法,而把value賦值為123的putstatic指令是程式被編譯後,存放於類別構造器()方法之中,所以把value賦值為12的動作將在初始化階段才會被執行。

上面所說的「通常情況」下初始值是零值,那相對於一些特殊的情況,如果類別欄位的欄位屬性表中存在ConstantValue屬性,那在準備階段變數value就會被初始化為ConstantValue屬性所指定的值,建置上面類別變數value定義為:

public static final int value = 123;

編譯時javac將會為value產生ConstantValue屬性,在準備階段虛擬機器就會根據ConstantValue的設定將value設定為123。


解析:

 解析階段是虛擬機器常數池內的符號引用替換為直接引用的過程。

符號引用:符號引用是一組符號來描述所引用的目標對象,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機器實現的記憶體佈局無關,引用的目標物件並不一定已經載入到記憶體中。

直接引用:直接引用可以是直接指向目標物件的指標、相對偏移或是一個能間接定位到目標的句柄。直接引用是與虛擬機器記憶體佈局實現相關的,同一個符號引用在不同虛擬機器實例上翻譯出來的直接引用一般不會相同,如果有了直接引用,那引用的目標必定已經在記憶體中存在。

虛擬機規範並沒有規定解析階段發生的具體時間,只要求了在執行anewarry、checkcast、getfield、instanceof、invokeinterface、invokespecial、invokestatic、invokevirtual、multianewarray、new、putfield和putstatic這13個用於操作符號在引用的字節碼指令之前,先將它們所使用的符號引用進行解析,所以虛擬機器實作會根據需要來判斷,到底是在類別被載入器載入時就對常數池中的符號引用進行解析,還是等到一個符號引用將要被使用前才去解析它。

解析的動作主要針對類別或介面、欄位、類別方法、介面方法四類符號參考進行。分別對應編譯後常數池內的CONSTANT_Class_Info、CONSTANT_Fieldref_Info、CONSTANT_Methodef_Info、CONSTANT_InterfaceMethoder_Info四種常數型別。

1.類別、介面的解析

2.字段解析

3.類別方法解析

4.介面方法解析


初始化: 在準備階段,類別變數已賦過一次系統要求的初始值,而在初始化階段,則是根據程式設計師透過程式製定的主觀計畫去初始化類別變數和其他資源,或者可以從另一個角度來表達:初始化階段是執行類別建構器()方法的過程。在以下四種情況下初始化過程會被觸發執行:

1.遇到new、getstatic、putstatic或invokestatic這4條字節碼指令時,如果類別沒有進行過初始化,則需先觸發其初始化。產生這4條指令的最常見的java程式碼場景是:使用new關鍵字實例化物件、讀取或設定一個類別的靜態欄位(被final修飾、已在編譯器把結果放入常數池的靜態欄位除外)的時候,以及呼叫類別的靜態方法的時候。

2.使用java.lang.reflect套件的方法對類別進行反射調用的時候

3.當初始化一個類別的時候,如果發現其父類別還沒有進行過初始化、則需要先出發其父類的初始化

4.jvm啟動時,使用者指定執行的主類別(包含main方法的那個類別),虛擬機會先初始化這個類別

在上面準備階段public static int value  = 12;  在準備階段完成後value的值為0,而在初始化階呼叫了類別建構器()方法,這個階段完成後value的值為12。

*類別建構子()方法是由編譯器自動收集類別中的所有類別變數的賦值動作和靜態語句區塊(static區塊)中的語句合併產生的,編譯器收集的順序是由語句在來源檔案中出現的順序所決定的,靜態語句區塊中只能存取到定義在靜態語句區塊之前的變量,定義在它之後的變量,在前面的靜態語句快可以賦值,但是不能存取。

*類別建構子()方法與類別的建構子(實例建構子()方法)不同,它不需要明確呼叫父類別建構,虛擬機會保證在子類別( )方法執行前,父類別的()方法已經執行完畢。因此在虛擬機器中的第一個執行的()方法的類別肯定是java.lang.Object。

*由於父類別的()方法先執行,也就表示父類別中定義的靜態語句快要優先於子類別的變數賦值運算。

*()方法對類別或介面來說並不是必須的,如果一個類別中沒有靜態語句,也沒有變數賦值的運算,那麼編譯器可以不為這個類別產生()方法。

*介面中不能使用靜態語句區塊,但介面與類別不太能夠的是,執行介面的()方法不需要先執行父介面的()方法。只有當父介面中定義的變數被使用時,父介面才會被初始化。另外,介面的實作類別在初始化時也一樣不會執行介面的()方法。

*虛擬機會保證一個類的()方法在多線程環境中被正確加鎖和同步,如果多個線程同時去初始化一個類,那麼只會有一個線程執行這個類的 ()方法,其他執行緒都需要阻塞等待,直到活動執行緒執行()方法完畢。如果一個類別的()方法中有耗時很長的操作,那就可能造成多個進程阻塞。

 以上就是Java虛擬機器學習 - 類別載入機制 的內容,更多相關內容請關注PHP中文網(www.php.cn)!

相關文章:

Java虛擬機的具體詳情

深入理解Java虛擬機

Java虛擬機學習物件分配

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn