JVM : 類加載機制

加載—連接(驗證—準備—解析)—初始化—使用—卸載
加載、驗證、準備、初始化和卸載的順序是確定的,類的加載過程必須按照這種順序按部就
班地開始,而解析階段則不一定:它在某些情況下可以在初始化階段之後再開始,爲了支持
java語言的運行時綁定。
這些階段通常都是互相交叉的混合的使用
以下五種情況必須立即對類進行“初始化”
(1)遇到new、getstatic、putstatic或invokestatic四條字節碼指令
(2)使用java.lang.reflect包的方法對類進行反射調用的時候
(3)當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類
的初始化
(4)當虛擬機啓動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),
虛擬機會先初始化這個主類
(5)
首先,在代碼編譯後,就會生成JVM(Java虛擬機)能夠識別的二進制字節流文件
(*.class)。而JVM把Class文件中的類描述數據從文件加載到內存,並對數據進行校驗、
轉換解析、初始化,使這些數據最終成爲可以被JVM直接使用的Java類型,這個說來簡單

1、加載
通常所說的加載大多不是指的類加載機制,只是類加載機制中的第一步加載。
在這個階段,JVM主要完成三件事:
(1)通過一個類的全限定名(包名和類名)來獲取定義此類的二進制字節流(Class文
件)。獲取方式:jar包,war包,通過網絡獲取、jsp文件生成等方式。
(2)將字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構。(方法區就是用來
存放已被加載的類型,常量,靜態常量,編譯後的代碼的運行時內存區域)
(3)在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據
的訪問入口。這個Class對象並沒有規定是在java堆內存中,它比較特殊,雖爲對象,但是
存放在方法區
2、驗證
是連接階段的第一步,這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當
前虛擬機的要求,並且不會危害虛擬機自身的安全。
驗證大致上會完成四個階段的檢驗動作:
(1)文件格式驗證:保證輸入的字節流能正確地解析並存儲於方法區之內
(2)元數據驗證:對類的元數據信息進行語義檢驗,保證不存在不符合java語言規範的元
數據信息
(3)字節碼驗證:通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。
(4)符號引用驗證:發生在虛擬機將符號引用轉化爲直接引用的時候,這個轉化動作將在
連接的第三階段——解析階段中發生,目的是確保解析動作能正常執行
3、準備
正式爲類變量分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中
進行分配。
這個時候進行內存分配僅包括類變量(被static修飾的變量),而不包括實例變量,實例變
量將會在對象實例化時隨着對象一起分配在java堆中。
4、解析
將類的二進制數據中的符號引用換爲直接引用
5、初始化
到了初始化階段,才真正開始執行類中定義的java程序代碼(或者說是字節碼)
初始化階段是執行類構造器<clinit>()方法的過程
(1)靜態語句塊中只能訪問定義在靜態語句塊之前的變量,定義在它之後的變量,在前面
的靜態語句塊可以賦值,但是不能訪問
public class Test{
static{
i=0;//給變量賦值可以正常編譯通過
System.out.print(i); //“非法向前引用”
}
static int i=1;
}
(2)虛擬機會保證在子類的<clinit>()方法執行之前,父類的<clinit>方法已經執行完
畢。所以虛擬機中第一個被執行的<clinit>()方法的類肯定是java.lang.Object
(3)父類中定義的靜態語句塊要優於子類的變量賦值操作
(4)如果一個類中沒有靜態語句塊,編譯器可以不爲這個類生成<clinit>()方法
(5)接口中不能使用靜態語句塊,但仍然有變量初始化的賦值操作,因此也會與類一樣生
成<clinit>()方法,但執行接口的<clinit>()的方法不需要先執行父類的<clinit>()
的方法,只有當父類的接口中定義的變量使用時,父接口才會初始化,同時,接口的實現類
在初始化時也一樣不會執行接口的<clinit>()的方法。
(6)如果多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的
<clinit>()的方法。

對於任意一個類,都需要由記載它的類加載器和這個類本身一同確定其在java虛擬機中的唯
一性
比較兩個類是否相等,前提是兩個類由同一個類加載器加載
否則即使兩個類來源於同一個Class文件,被同一個虛擬機加載,只要加載他們的類加載器
不同,那麼這兩個類就必定不相等

雙親委派模型:
除了頂層的啓動類加載器,其餘的類加載器都應當有自己的父類加載器,採用組合關係來複
用父類加載器的代碼
工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而
是把這個請求委派給父類的加載器去完成,因此所有的加載請求都應該傳送到頂層的啓動加
載器中,只有當父類加載器反饋自己無法完成這個加載請求時,子加載器纔會嘗試自己去加

使用雙親委派模型來組織類加載器之間的關係,有一個顯而易見的好處就是java隨着它的類
加載器一起具備了一種帶有優先級的層次關係。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章