標籤:執行個體化 代理 str 進位 運行時 分發 定位 不能 屬性工作表
虛擬機器把描述類的資料從Class檔案載入道記憶體,並對資料進行校正,轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java類型,這就是虛擬機器的類載入機制。在Java裡,類型的載入、串連和初始化過程都是在程式運行期間完成的,這種策略雖然會令類載入時稍微增加一些效能開銷,但是會為Java應用程式提供高度的靈活性。
一、類載入的時機
類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個階段。其中驗證、準備、解析3個部分統稱為串連。
類的初始化
虛擬機器規範嚴格規定了有且只有5種情況必須立即對類進行“初始化” (載入、驗證、準備必須要在此前開始):
1) 使用new關鍵字執行個體化對象的時候,讀取或設定一個類的靜態欄位的時候(被final修飾、已在編譯期把結果放入常量池的靜態欄位除外),以及調用一個類的靜態方法的時候。(一個變數被final修飾且是編譯時間確定的。那麼這個變數可稱為宏變數。宏變數可以將 引用指向常量池)
2) 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
3) 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化
4) 當虛擬機器啟動時,使用者需要指定一個要執行的主類(main class),虛擬機器會先初始化這個主類。
5) 當使用JDK_1.7的動態語言支援時,如果一個java.lang.invoke.MethodHandle執行個體最後的解析結果是REF_getStatic,REF_putStatic,REF_invokeStatic的方法控制代碼,並且這個方法控制代碼所對應的類沒有進行過初始化,則需要先觸發其初始化。
介面和類的初始化僅有一點不同,一個介面在初始化的時候並不要求其父介面全部完成了初始化,只有在真正使用到父介面的時候(如引用介面中定義的常量)才會初始化。
類的被動引用
1) 對於靜態欄位,只有定義這個欄位的類才會被初始化,因此通過子類來引用父類中定義的靜態欄位只會初始化其父類而非子類。
2) 通過數組定義來引用類,不會觸發此類的初始化。SupperClass[] s = new SupperClass[10];
3) 常量在編譯階段會存入調用類的常量池中,本質上並沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化。public static final String HELLO = "hello";
二、類載入的過程載入
載入是類載入過程的一個階段,在載入階段,虛擬機器需要完成以下三件事:
1) 通過一個類的全限定名來擷取定義此類的二進位位元組流
2) 將這個位元組流所代表的靜態儲存結構轉化為方法區的運行時資料結構
3) 在記憶體中產生一個代表這個類的java.lang.Class對象,作為方法區這個類的各種資料的訪問入口
“通過一個類的全限定名來擷取定義此類的二進位位元組流“沒有指明二進位位元組流要從哪裡或怎樣擷取,並非一定從Class檔案擷取,許多Java技術都建立在這一基礎之上:
1) 從ZIP包中讀取(JAR、EAR、WAR)
2) 從網路中擷取(Applet)
3) 運行時計算產生(動態代理技術)
4) 由其他檔案產生(JSP檔案產生對應的Class類)
5) 從資料庫讀取(比較少見,有些中介軟體伺服器把程式安裝到資料庫來完成程式碼在叢集間的分發)
載入階段完成後,虛擬機器外部的二進位位元組流就按照虛擬機器所需的格式儲存在方法區之中。然後在記憶體中執行個體化一個java.lang.Class類的對象(並沒有明確規定是在Java堆中,對於Hotspot虛擬機器而言,Class對象雖然是對象但是存放在方法區裡面),這個對象將作為程式存取方法區中這些類型資料的外部介面。
載入階段與串連階段的部分內容(如一部分位元組碼檔案格式驗證動作)是交叉進行的,但這兩個階段的開始時間仍然保持著固定的先後順序。
驗證
驗證是串連階段的第一步,這一階段的目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。
1) 檔案格式驗證 (主要驗證位元組流是否符合Class檔案格式的規範)
2) 中繼資料驗證 (對位元組碼描述的資訊進行語義分析,以保證其符合Java文法要求,如final不能被重寫等)
3) 位元組碼驗證 (最複雜的階段,保證校正類的方法在運行時不會做出危害虛擬機器安全的事件)
4) 符號引用驗證 (符號引用中的類、欄位、方法是否可被當前類訪問,private, protected等)
準備
準備階段是正式為類變數分配記憶體並設定類變數初始值的階段,這些變數使用的記憶體都將在方法區中進行分配。這時進行記憶體配置的僅包括類變數(static修飾的變數)而不包括執行個體變數,執行個體變數在Java堆中。這裡的初始值是資料類型的零值(int為0)。但如果類欄位的欄位屬性工作表中存在ConstantValue屬性,則此變數值就會初始化為ConstantValue所指定的值(static final int)。
解析
解析階段是虛擬機器將常量池內的符號引用替換為直接引用的過程。
符號引用(Symbolic References): 以一組符號來描述所引用的目標,符號引用與虛擬機器實現的記憶體布局無關,引用的目標不一定已經載入到記憶體中。符號引用明確定義在Java虛擬機器規範的Class檔案格式中。
直接引用(Direct References): 與虛擬機器實現的記憶體布局相關,可以是直接指向目標的指標、相對位移量或是一個能間接定位到目標的控制代碼。如果有了直接引用,則引用目標必定已經在記憶體中存在。
初始化
類初始化階段是類載入過程的最後一步,才真正開始執行類中定義的Java程式碼(或者說是位元組碼)。初始化階段是執行類構造器<clinit>()方法的過程。
深入理解Java虛擬機器- 學習筆記 - 虛擬機器類載入機制