目錄
類加載的概述
我們將開發好的的代碼打包爲jar或者編譯爲class文件包,交給tomcat、apache等web服務器,操作系統會啓動一個java虛擬機(Java Virtual Machine,JVM)進程來運行web服務器。
Java語言經過編譯、翻譯、轉化,最終變成了可以被計算機直接識別和接受的機器指令。
我們編譯好的類文件會以二進制流的形式加載到虛擬機中,不同的虛擬機進程相互獨立、互不影響。
java語言是在程序運行期完成連接工作的,這種模式的好處是在開發、編譯打包階段不需要將所用到的文件都聚在一起,例如:如果編寫一個面向接口的應用程序,可以等到運行時再指定其實際的實現類。用戶可以通過Java預定義和自定義類加載器,讓一個本地應用程序可以在運行時從網絡或者其他地方加載一個二進制流作爲程序的一部分。C語言時編譯期連接的語言
類加載的時機
類加載是一個組合過程,從加載到卸載爲止,整個生命週期包括:加載(Loading)、驗證(Verification)、準備(Perparation)、解析(Resoulution)、初始化(Initlization)、使用(Using)、卸載(Unloading)七個階段。其中解析階段可能不是按照如下順序執行,在某些情況下可以在初始化之後的階段再執行。
加載階段的詳解
1.加載(Loading)
1.1類加載的定義
類的加載是指將類的.class文件以二進制形式讀入到內存中,將其放在運行時數據區的方法區內,然後在內側中創建一個java.lang.class對象,用來封裝類在方法區內的數據結構,class對象可以看作是對內存的指向,相當於鏡像。類加載其實是對類型的加載,並不是對實例化結果加載。
1.2加載途徑
- 從本地系統中直接加載
- 通過網絡加載 (applet應用)
- zip、jar加載
- 從專有數據庫加載
- 運行時計算生成,動態代理技術
1.3數組類加載
數組類本身不通過類加載器創建,它是由Java虛擬機直接創建的。數數組類的元素類型最終要考類加載器創建,一個數組類的創建過程遵循以下規則:
如果數組的組件類型是引用類型,那就遞歸採用加載過程去加載這個組件類型,數組類將在加載該類組件類型的類加載器的類名稱空間上被標示。
- 如果數組的組件類型不是引用類型,Java虛擬機將會把數組類標記爲與引導類加載器關聯。
- 數組類的可見性與它的組件類型的可見性一致,如果組件類型不是引用類型,那數組類的可見性將默認爲public。
加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲載方法區之中,然後在內存中實例化一個java.lang.Class對象作爲外部訪問接口。
2.驗證(Verification)
驗證是連接階段的第一步,這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身安全。驗證階段可以分爲四步檢驗動作:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。對於虛擬機的類加載機制來說,驗證階段十分重要,但是如果所運行的代碼以及被反覆驗證多次,可以使用-Xverify:none參數來關閉驗證,縮短加載時間。
2.1文件格式驗證
- 是否以魔數0XCAFBABE開頭(魔數可以在作文件上傳模塊中做安全驗證)
- 主次版本號是否在當前虛擬機處理範圍內
- 常量池中的常量是否有不被支持的常量類型(檢查常量tag標誌)
- 指向常量的各種索引值是否有執行不存在的常量或不符合類型的常量
- CONSTANT_Utf8_info型的常量中是否包含有不符合UTF8編碼的數據
- Class文件中各個部分及文件本身是否有被刪除的或附加的其他信息
2.2元數據驗證
- 加載的類是否有父類(除了java.lang.Object之外,所有的類都應該有父類)
- 這個類的負累是否都繼承了不被允許繼承的類(被final修飾的類)
- 如果這個類不是抽象類是否實現了其父類或接口之中要求實現的所有方法
- 類中的字段、方法、是否和父類產生矛盾(例如覆蓋了父類的final字段,或者出現不符合規則的方法重載)
2.3字節碼驗證
- 保證任意時刻操作數棧的數據類型與指令代碼序列都能配合工作,反例:載操作棧放置了一個int類型的數據,使用時卻按long類型來加載入本地變量表中
- 保證跳轉指令不會跳轉到方法體以外的字節碼指令上
- 保證方法體中的類型轉換是有效的
在Java 6版本之後JVM在class文件中引入了棧圖(StackMapTable)屬性。作用是爲了提高JVM在字節碼驗證過程的效率。
2.4符號引用驗證
- 符號引用中通過字符串描述的全限定名是否能找到對應的類
- 載指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段
- 符號應用的類、字段、方法的訪問性是否被當前類支持訪問
3.準備(Perparation)
準備階段是正式爲類變量分配內存並且設置類變量初始值的階段,這裏進行內存分配的僅包含類變量(佩static修飾的變量),不包含實例變量,這裏所說的初始值“通常情況”下指數據類型的零值。
數據類型 | 零值 |
---|---|
int | 0 |
long | 0L |
short | (short)0 |
char | '\u0000' |
byte | (byte)0 |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
4.解析(Resoulution)
解析階段是虛擬機將常量池內的符號引用替換爲直接引用過程,解析階段發生在執行了用於操作符號引用的字節碼指令之前。
符號引用:以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義的定位到目標即可
直接引用:直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄
5.初始化(Initlization)
初始化階段是程序真正按照程序員設定去初始化類變量和其他資源,從程序語言描述就是執行類構造器<clinit>()方法的過程,簡單描述就是爲類的靜態變量賦於正確的初始值
參考資料:
周志明《深入理解java虛擬機》
孫衛琴《Tomcat與Java Web開發技術詳解》