虛擬機類加載機制
虛擬機把描述的類的數據從class文件加載到內存後,並對數據進行校驗,轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。
類加載的時機
類被加載到虛擬機內存開始,到卸載出內存為止。它的整個生命周期包括:類加載(Loading),驗證(Verification),准備(Preparation),解析(Resolution),初始化(Initialization),使用(Using)和卸載(Unloading)7個階段。其中驗證,准備,解析3個部分統稱為連接(Linking)。
虛擬機規范嚴格規定了有且僅有5種情況必須立即對類進行“初始化”:
1.遇到new , getstatic , putstatic 或involvestatic這4條字節碼指令時,如果類沒有進行過初始化,則需要先觸發其初始化。
2.使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
3.當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
4.當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main方法的類),虛擬機會先初始化這個類。
5.當使用JDK1.7的動態語言支持時,如果java.lang.invoke.MethodHeadle實例,最後的解析結果REF_getstatic , REF_putstatic , REF_invokestatic的方法句柄,並且這個方法句柄所對應的類沒有進行過初始化,則需要先觸發其初始化。
類加載的過程
一、加載
(1)在加載階段,虛擬機需要完成以下3件事:
1.通過一個類的全限定名來獲取定義此類的二進制字節流
2.將這個字節流所代表的靜態存儲結構結構轉化為方法區的運行時數據結構
3.在內存中生成一個代表這個類的java.lang.class對象,作為方法區這個類的各種數據的訪問入口
(2)數組類本身不通過類加載器創建,他是由Java虛擬機直接創建的。
一個數組類創建過程遵循以下規則:
1.如果數組的組件類型(Component Type , 指的是數組去掉一個維度的類型)是引用類型,那就遞歸采用上面介紹的加載過程去加載這個組件類型,數組將在加載該數組組件類型的類加載器的類名稱空間上呗標識。
2.如果數組的組件類型不是7引用類型,Java虛擬機將會把數組標記為與引導類加載器關聯。
3.數組類的可見性與它的組件類型的可見性一致,如果組件類型不是引用類型,需要數組類的可見性將 默認為public
二、驗證
驗證是連接階段的第一步,這一階段的目的是為了確保class文件的字節流包含的信息符合當前虛擬機的要求,並且不會危害虛擬機本省的安全。
驗證階段大致上會完成以下4個階段的檢驗動作:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證
(1 ) 文件格式驗證
1.第一階段要驗證字節流是否符合class文件格式的規范,並且能被當前版本的虛擬機處理。
2.中油通過了這個階段的驗證後,字節流才會進入內存的方法區中進行存儲,所以後面的3個驗證階段全部是基於方法區的存儲結構進行的,不會直接操作字節碼。
(2 ) 元數據驗證
1.第二階段是對字節碼描述的信息進行語義分析,以確保其描述的信息符合Java語言規范的要求。
2.第二階段的主要目的是對類的元數據信息進行語義化驗,保證不存在不符合Java語言規范的元數據信息
(3 ) 字節碼驗證
1.督三階段是整個驗證過程中最復雜的一個階段,主要目的是通過數據流和控制流分析,確定程序語義是合法的,符合邏輯的。這個階段將對類的方法體進行校驗分析。保證被校驗類的方法運行時不會做出危害虛擬機安全的時間。
2.例如:保證任意時刻操作數棧的數據類型與指令代碼序列都能配合工作。保證跳轉到方法體以外的字節碼指令上。
(4 ) 符號引用驗證
1.最後一個驗證階段的檢驗發生在虛擬機將符號引用轉化為直接引用的時候,這個轉化動作將在連接階段的第三階段——解析階段中發生。符號引用驗證可以看做是對類自身以外的信息進行匹配性校驗。
2.例如:符號引用中通過字符串描述中的全限定名是否能找到對應的類。在特定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段
3.符號引用驗證的目的是確定解析動作能正常執行,如果無法通過符號引用驗證,那麼將會拋出java.lang.IncompatibleClassChangeError異常的子類
三、准備
准備階段是正式為類變量 分配內存並設置類變量初始值的階段,這些變量所使用的內存將在方法區中進行分配。
四、解析
(1)解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。
1.符號引用(symbolic Reference):符號引用逸一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用 時能無歧義地定位到引用目標即可。符號引用與虛擬機實現的內存布局無關,引用的目標並不一定已經加載到內存中。各種虛擬機實現的內存布局可以各不相同,但是它們能接受的符號引用必須是一致的,因為符號引用的字面量形式明確定義在Java虛擬機規范中的class文件格式中。
2.直接引用(Direct Reference):直接引用可以是直接指向目標的指針。相對偏移量或是一個能間接定位到目標的句柄。直接引用是和虛擬機實現的內存布局相關的,同一個符號引用在不同的虛擬機實例上翻譯出來的直接引用一般不會相同,如果有了直接引用那引用的目標必定已經在內存中存在。
(2)虛擬機規范中並未規定解析階段發生的具體時間,只要求了執行anewarray,checkcast,getfield,getstatic,instanceof,invokedynamic,invokeinterface,invokespecial,invokestatic,invokevirtual,ldc,ldc_w,multianewarray,new,putField和putstatic這16個用於操作符號引用的字節碼指令之前,先過它們所使用的符號引用進行解析。
(3)解析動作主要針對類或接口,字段,類方法,接口方法,方法類型,方法句柄和調用點限定符7類符號引用進行。
(4)類或接口的解析
虛擬機完成整個解析的過程需要以下3個步驟
1)如果c不是一個數組類型,那虛擬機將會把代表N的全限定名傳遞給D的類加載器去加載這個類C。在加載過程中,由於元數據驗證,字節碼驗證的需要,又可能觸發其他相關的類的加載動作。
2)如果C是一個數組類型,並且數組的元素類型為對象,那將會按以上的規則加載數組類型。如果N的描述符如前面所假設的形式,需要加載元素的類型,接著由虛擬機生成一個代表此數組維度和元素的數組對象。
3)如果上面的步驟沒有出現任何異常,那麼C在虛擬機中實際上已經成為一個有效的類或接口了,但在解析完成之後還要進行符號引用驗證,確認D是否是具備對C的訪問權限。
(5)字段解析
(6)類方法解析
(7)接口方法解析
五、初始化
1.類初始化階段是類加載過程的最後一步
2.在准備階段,變量已經賦過一次系統要過的初始值,而在初始化階段,則根據程序員制定的主觀去初始化變量和其他資源,或者可以從另外一個角度來表達:初始化階段是執行類構造器<cninit>()方法的過程。
3.<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊(static{}塊)中的語句合並產生的,編譯器收集的順序是由語句在源文件中出現的順序所決定的,靜態語句塊只能訪問到定義在靜態語句塊之前的變量,定義在它之後的變量,在前面的靜態語句塊可以賦值,但是不能訪問。
4.<clinit>()方法與類的構造函數不同,它不需要顯式地調用父類構造器,虛擬機會保證在子類的<clinit>()方法執行之前,父類的<clinit>()方法已經執行完畢。因此在虛擬機中第一個被執行的<clinit>()方法的類肯定有java.lang.object。
5.由父類的<clinit>()方法先執行,也就意味著父類中定義的靜態語句塊要優先於子類的變量賦值操作。
6.<clinit>()方法對於類或接口來說並不是必須的,如果一個類中沒有靜態語句塊,也沒有對變量的賦值操作,那麼編譯器可以不為這個類生成<clinit>()方法
7.虛擬機會保證一個類的<clinit>()方法在多個線程環境中正確地加鎖,同步。
類加載器
一、類與類加載器
對於任意一個類,需要由加載它的加載器和這個類本身一同確立其在Java虛擬機中的唯一性,每一個類加載器,都擁有一個獨立的類名稱空間。
二、雙親委派模型
(1)從Java虛擬機的角度來講,只存在兩種不同的類加載器:一種是啟動類加載器(Bootstrap Class ClassLoader),這個類加載器使用C++語言實現是Java虛擬機自動的一部分;另一種就是所有其他的類加載器,這些類加載器都由Java語言實現,獨立於虛擬機外部,並且全都是繼承自抽象類java.lang.classLoader.
(2)細分:啟動類加載器(Bootstrap ClassLoader)
擴展類加載器(Extension ClassLoadert)
應用程序類加載器(Application ClassLoader)[系統類加載器]
(3)雙親委派模型工作過程:
如果一個類加載器收到了類加載器的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層類加載器中只有當父類加載器反饋自己無法完成這個類加載請求時,子類加載器才會嘗試自己去加載。