在馮諾依曼定義的計算機模型中,任何程序都需要加載到內存中才能與CPU進行交流。字節碼.class文件同樣需要加載到內存中,才可以實例化類。
ClassLoader類加載器負責將提前加載.class類文件到內存中,使用雙親委派機制
1、類加載的時機
類從被加載到虛擬機內存中開始,到卸載出內存為止,整個生命周期包括:加載、驗證、准備、解析、初始化、使用和卸載7個階段。
其中驗證、准備、解析3個部分統稱為連接。
對於初始化階段,虛擬機規范嚴格規定了有且只有5種情況必須對類進行初始化(加載、驗證、准備自然需在此之前開始):
1、遇到new、getstatic、putstatic或invokestatic這4條字節碼指令時,如果類沒有進行過初始化,則需要先觸發其初始化。生成這四條指令最常見的Java場景是:
1):使用關鍵字new實例化對象的時候
2):讀取或設置一個類的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候
3):調用一個類的靜態方法的時候
2、使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化
3、當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化
4、當虛擬機啟動的時候,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個類
5、當使用JDK1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄
並且這個方法句柄對應的類沒有進行過初始化,則需要先觸發其初始化。
對於這5種觸發類進行初始化的場景,虛擬機規范中使用了一個很強烈的限定語:“有且只有”,這5種場景中的行為稱為對一個類進行主動引用。除此之外,所有引用類的方式都不會
觸發初始化,稱為被動引用。如:
1):通過子類引用父類的靜態字段,不會導致子類初始化
2):通過數組定義來引用類,不會觸發此類的初始化
3):常量(static final 修飾的)在編譯階段會存入調用類的常量池中,因此調用其常量本質上並沒有直接引用到定義常量的類,因此不會觸發常量的類的初始化
2、類加載的過程
也就是加載、驗證、准備、解析、初始化這5個階段所執行的具體操作
1):加載
在加載階段,虛擬機需要完成以下3件事情:
(1):通過一個類的全限定名來獲取定義此類的二進制流
(2):將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構
(3):在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口
一個非數組類的加載階段獲取類的二進制字節流可以通過系統提供的引導類加載器完成,也可以由用戶自定義的類加載器去完成,開發人員可以自定義類加載器重寫loadClass()方法
去控制字節流的獲取方式。
數組類情況則不同,數組類本身不通過類加載器創建,而是由Java虛擬機直接創建的。但數組類與類加載器仍然有很密切的關系,因為數組類里的元素類型是要靠類加載器去創建。
加載階段完成之后,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,方法區中的數據存儲格式由虛擬機實現自行定義,虛擬機規范未規定此區域的具體數據
結構。然后在內存中實例化一個java.lang.Class類的對象,(並沒有明確規定是在Java堆中,對於HotSpot虛擬機而言,其有點特殊,Class雖是一個對象,但卻存放在方法區里面)
,這個對象作為程序訪問方法區中的這些類型數據的外部接口。
2):驗證
驗證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全
(1):文件格式驗證
(2):元數據驗證
(3):字節碼驗證
(4):符號引用驗證
如final是否合規,類型是否正確,靜態變量是否合理等
3):准備
准備階段是正式為類變量分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。這個階段中有兩個容易產生混淆的概念需要強調一下,首先,這時候進行內存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量會在對象進行初始化時隨着對象一起分配在java堆中。其次,這里所說的初始值通常情況下是數據的零值,即數據字段類型的默認值,而不是實際賦的值。
4):解析
解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。
符號引用:符號引用包括了三種常量,分別是:類和接口的全限定名,字段的名稱和描述符,方法的名稱和修飾符
直接引用:直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄,直接引用的目標一定在內存中存在。
5):初始化
獲取到Class對象初始化鎖的線程才能只能初始化,其他線程都要阻塞等待。主要工作:
給類變量(靜態變量)賦定義的值
執行靜態代碼塊(靜態代碼塊只能訪問定義在靜態代碼塊之前的靜態變量,定義在靜態代碼塊之后的靜態變量,可以賦值,但是不能訪問)
總結:
在類加載的准備階段即為類變量(被static修飾的變量)在方法區分配內存並設置類變量的初始值(類型的默認值)。
靜態常量(即用 static final 修飾的變量)是編譯時常量,在編譯期即把結果放入常量池