search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

類載入的過程

類的載入過程分為5個步驟:載入、驗證、準備、解析、初始化

其中的驗證、準備、解析階段又統稱為連接,如下圖所示。

在這5個階段中,載入、驗證、準備、初始化這4個階段的順序是確定的,類的載入過程必須按照這種順序按部就班地開始,而解析階段則不一定,為了支持java語言的運行時綁定,它在某些情況下可以在初始化階段之後再開始。

這裡之所以說按部就班地開始,而不是按部就班地「進行」或「完成」,是因為這些階段通常可以交叉混合進行,如在載入階段執行過程中,也會同時執行驗證階段。

什麼時候要開始一個類的類載入?

什麼情況下需要開始類載入過程的第一步「載入」?Java虛擬機並沒有進行強制約束,這點可以由虛擬機自行實現。

但是對於初始化階段,虛擬機規範則進行了嚴格規定,有且只有在以下7種情況下,必須立即對類進行初始化(而載入、驗證、準備自然需要在此之前開始):

  • 使用new關鍵字實例化對象的時候
  • 設置或讀取一個類的靜態欄位(被final修飾,已在編譯器把結果放入常量池的靜態欄位除外)的時候
  • 調用一個類的靜態方法的時候
  • 使用java.lang.reflect包的方法對類進行反射調用的時候
  • 初始化一個類的子類(會首先初始化父類)
  • 當虛擬機啟動的時候,初始化包含main方法的主類
  • 當使用jdk1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,並且這個方法句柄所對應的類沒有進行過初始化,則需要先觸發其初始化。

類載入過程詳解載入

在載入階段,虛擬機需要完成以下3件事情:

  • 通過一個類的全限定名來獲取此類的二進位位元組流
  • 將這個位元組流所代表的靜態存儲結構轉化為方法區的運行時數據結構
  • 在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的入口(雖然是對象,但並沒有明確規定要存在java堆中,HotSpot的實現是存在了方法區)

驗證

驗證階段大致分4個階段:

  • 文件格式驗證
  • 元數據驗證
  • 位元組碼驗證
  • 符號引用驗證

此階段的驗證是基於二進位位元組流進行的,只有通過了這個階段的驗證后,位元組流才會進入內存的方法區中進行存儲,所以後面3個階段全部是基於方法區的存儲結構進行的,不再直接操作位元組流。(從這裡也可以看出,驗證階段是和載入階段一起進行的,只有當驗證階段完成後,載入階段的第二個步驟將位元組流轉儲為方法區的運行時數據結構才能完成)

準備

準備階段是正式為類變數分配內存並設置類變數初始值。

需要注意的是,類似於:

「public static int value=123」 這種定義,在準備階段過後的初始值是0而不是123,把value賦值為123需要等到初始化階段再執行。

但是如果是:

「public static final int value=123」 這種定義,編譯時javac會給value生成ConstantValue屬性,這種情況下在準備階段虛擬機就會根據ConstantValue將value賦值為123。

解析

解析階段是虛擬機將常量池內的符號引用轉化為直接引用的過程。

  • 類或介面的解析
  • 欄位解析
  • 類方法解析
  • 介面方法解析

初始化初始化階段是執行類構造器方法的過程。

方法是由編譯器自動收集類中的靜態變數賦值動作和靜態語句塊(static{})中的語句合併生成的,編譯器的收集順序由語句在源文件中出現的順序決定。

需要注意的有兩點:

  • 在執行類的方法之前,如果類有父類,則先執行父類的方法
  • 與類不同,在執行介面的方法之前,如果介面有父介面,不需要先執行父類的方法。

類載入器

在類載入的第一個階段--「載入」階段,第一個動作是:「通過一個類的全限定名來獲取此類的二進位位元組流」,我們把實現這個動作的代碼模塊稱為「類載入器」。

類載入器最初是為了滿足Java Applet的需求而開發的,雖然目前Java Applet基本已經「死掉」,但是類載入器卻在類層次劃分、OSGi、熱部署、代碼加密等領域大放異彩,成為Java體系中的一塊重要基石。

必須了解的一個概念是:

對於一個類,這個類本身和載入它的類載入器一同確立其在Java虛擬機的唯一性。

也就是說,不同類載入器載入同一個Class文件所產生的兩個類是不相等的,體現在Class對象的equals方法、instanceof關鍵字的判定等。

類載入器分類

  • 啟動類載入器(Bootstrap ClassLoader):負責將JAVA_HOME/lib 目錄下的,或者被-Xbootclasspath參數所指定的路徑中的,被虛擬機識別的類庫(僅按文件名識別,如rt.jar,名字不符合的類庫不會載入)載入到虛擬機內存中。啟動類載入器無法被Java程序直接引用。
  • 擴展類載入器(Extension ClassLoader):載入JAVA_HOME/lib/ext目錄下的,或者被java.ext.dirs系統變數所指定的路徑中的所有類庫,開發者可以直接使用擴展類載入器。
  • 應用程序類載入器(Application ClassLoader):也成為系統載入器。負責載入用戶類路徑(classpath)下所指定的類庫,開發者可以直接使用這個類載入器。如果應用程序中沒有自定義自己的類載入器,一般這個就是程序中默認的類載入器。

如圖:

圖中的類載入器之間的層次關係,稱為類載入器的雙親委派模型。

雙親委派模型要求除了頂層的啟動類載入器之外,其他的載入器都要有自己的父載入器。這裡類載入器之間的父子關係不以繼承而是以組合方式來實現。

雙親委派模型的工作過程是:每一個類載入器收到類載入請求,都會首先將請求委派到其父載入器去完成,只有當父載入器無法完成載入,子載入器才會嘗試自己去載入。

雙親委派模型的好處是Java類隨著它的載入器一起具備了優先順序的層級關係。如java.lang.Object,它存在於rt.jar,無論哪個類載入器要載入這個類,最終都要委派給頂層的啟動載入器,因此Object在程序的各類載入器環境中都是同一個類。即使用戶自己定義一個java.lang.Object類,也無法被載入。



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦