虛擬機是如何載入類的
一、概述
首先先來看幾個問題
- jvm是如何載入這些Class文件的?
- jvm載入一個Class文件需要哪些步驟?
- Class文件中的信息進入到虛擬機後會發生什麼變化?
接下來看看jvm載入class文件的概述:
jvm吧描述類的數據從class文件載入到內存,並對數據進行校驗,轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類載入機制。這句話差不多已經會上上面三個問題的大部分了。
與那些在編譯是需要進行鏈家工作的語言不通,在Java語言裡面,類型的載入和連接過程都是在程序運行期間完成的,這樣會在類載入是稍微增加一些性能開銷,但是卻能為Java應用程序提供高度的靈活性,Java中天使可以動態的擴展的語言特性就是依賴運行期動態載入和動態連接這個特點實現的。比如編寫一個使用介面的應用程序,可以等到運行時在指定其實際的實現。這種組裝應用程冠希的方式廣泛應用於Java程序之中。
二、要點類從被載入到jvm內存中開始,到卸載出內存為止,它的生命周期包括了一下步驟:載入(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Useing)和卸載(Unloading)七個階段。其中的驗證、準備和解析三個部分統稱為鏈接(Linking),這七個階段的發生順序如下圖,注意是發生的順序,不是執行完成的先後順序。
1、載入載入階段是「類載入」過程的一個階段,虛擬機需要做以下三件事:
- 通過一個雷的全限定名來獲取定義此類的二進位位元組流。
- 將這個位元組流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
- 在Java堆中生成一個代表這個類的java.lang.Class對象,作為方法區這些數據的入口。
載入階段完成後,虛擬機外部的二進位位元組流就按照虛擬機所需的格式存儲在方法區之中,方法區中的數據存儲格式有虛擬機實現自定義,虛擬機規範未規定此區域的具體數據結構。然後再Java堆中實例化一個java.lang.Class類的對象,這個對象作為程序訪問方法區中的這些類型數據的外部介面。載入階段與連接階段的部分內容是交替進行的,載入階段尚未完成,連接階段可能已經開始,但這些夾在載入階段之中進行的動作,仍然屬於連接階段的內容,這兩個階段的開始時間仍然保持著固定的先後順序。
2、驗證驗證階段虛擬機做了下面這些事情
1、文件格式驗證
第一階段是要驗證位元組流是否符合Class文件格式的規範,並且能被當前版本的虛擬機處理。會驗證一下這些內容。
- 主、次版本號是否在當前虛擬機處理範圍之內。
- 常量池的常量中是否有不被支持的常量類型。
- 指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量。
- CONSTANT_Utf8_info型的常量中是否有不符合UTF8編碼的數據。
- Class文件中各部分及文件本身是否有被刪除的或附加的其他信息
2、元數據的驗證
- 這個類是否有父類。
- 這個類的父類是否繼承了不允許被繼承的類(即被final修飾的類)。
- 若這個類不是抽象類,是否實現了其父類或介面之中要求的所有方法。
3、位元組碼的驗證
這個階段是驗證最為複雜的一個階段,主要工作是進行數據流和控制流分析,緊接第二階段。
- 保證任意時刻操作數棧的數據類型與指令代碼序列能配合工作。不會是在操作棧中放置了一個int類型的數據,使用時卻按照long類型來載入如不讓你弟變數表中。
- 保證跳轉指令不會跳轉到方法體之外的位元組碼上。
- 保證 方法體重的類型轉換是有效。
4、符號引用驗證
- 符號引用中通過字元串描述的全限定名是否能找到對應的類。
- 在知道類中是否存在符合方法的欄位描述符即簡單名稱所描述的方法和欄位。
- 符合引用中的類、欄位和方法的訪問級別是否可以被當前的類訪問。
3、準備
準備階段是正式為類變數分配內存並設置類變數初始值的階段,注意是初始值不是最終變數的值,都將在方法區中進行分配。如果該變數不是靜態變數,將不會進行內存分配,而是會在類出乎實話的時候隨著對象一起分配到Java堆中。另外這裡的初始值通常情況下是零值。具體的初始化的值見下圖,圖片來源於《深入理解Jvm虛擬機》。
4、解析
解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。
符號引用:符號引用以一組符號來描述所引用的目標,符號可以使任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用於虛擬機實現內存布局無關,引用的目標不一一定已經載入到內存中。
- 直接引用:直接引用可以使直接指向目標的指針、相對偏移量是一個能簡介定位到目標的句柄。
5、初始化
類初始化階段是類載入過程的最後一步,前面的類載入過程中,除了在載入階段用戶應用程序可以通過自定義類載入器參與之外,其餘動作完全有虛擬機主導和空值。到了初始化階段,才真正開始執行類中定義的Java程序代碼。
前面講到在準備階段變數已經富餘過一次初始值,而在初始化階段,則是根據程序員通過程序制定主觀計劃去初始化變數和其他資源。
三、初始化階段補充
一下四中情況會必須立即對類進行「初始化」。
- 遇到new、getstatic、putstatic或invokestatic者4調位元組碼指令時,如果類每一進行過初始化,則需要先觸發器初始化。
- 使用反射調用一個對象的時候,該對象必須初始化
- 當初始化一個類的時候發現其父類沒有初始化,則對其父類先初始化
- 當虛擬機啟動的時候,用戶需要知道一個要執行的主類(即包含main方法的那個類),虛擬機會縣初始化這個主類。
除了上面4中場景,都不會觸發初始化,稱為被動引用。
場景一
public class SupClass {
public static int value = 100;
static{
System.out.println("SupClass init...");
}
}
public class SubClass extends SupClass {
static{
System.out.println("SubClass init...");
}
}
客戶端代碼
public class InitTest {
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
輸出如下
SupClass init...
100
可以看到通過子類引用父類的靜態欄位,不會導致子類初始化。
場景二
其他代碼同場景一,客戶端代碼變成如下
public class InitTest {
public static void main(String[] args) {
SupClass sca = new SupClass[10];
}
}
這段代碼不會輸出任何結果。因為通過數組定義來引用類,不會觸發此類的初始化。
場景三
public class ConstClass {
public static final String HELLO = "hello";
static{
System.out.println("ConstClass init...");
}
}
客戶端代碼
public class InitTest {
public static void main(String[] args) {
System.out.println(ConstClass.HELLO);
}
}
輸出如下
hello
可以看到常量在編譯階段會存入調用類的常量池中,本質上沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化。
四、總結本篇文章依據以下兩點
- 在實際情況中,每個class文件都有可能代表著Java語言中的一個類或者介面,而對於類和介面需要分開描述
- 筆者所講的「Class文件」並非指class必須是存在於具體磁碟中的某個文件,這裡說的class文件指的是遺傳二進位位元組流,無論以何種形式存在都可以。
※WPF:實現 ScrollViewer 滾動到指定控制項處
※ElasticSearch裡面的路由功能介紹
※ThinkPHP自動生成的控制器類
※使用Github+Hexo框架搭建部署自己的博客
※ReactiveCocoa源碼解析(二)Bag容器的代碼實現
TAG:科技優家 |
※武當山機場迎來一架特別的飛機!將會載入史冊!
※7步解決手機應用載入慢,讓你手機更順暢
※一位因「荒於色,有禽獸行」被載入史冊的將軍,究竟幹了些什麼?
※固態硬碟將是下一代Xbox主機重要標配 載入界面或不復存在
※那些不被載入史冊的秘聞,竟然如此有趣!
※如何安裝Excel載入項插件?
※載入人類歷史的,不只是一張黑洞照片
※計算機的CPU為何不直接調取硬碟 而要先載入到內存?
※三款被載入史冊的高通驍龍660機型,它們徹底顛覆手機的定價策略
※裝機員幫你解決win7系統電腦開機一直卡在正在載入個人設置的界面
※LOL屏幕上突然彈出「載入成功」,於是匆忙下播,這是什麼情況?
※新主機PS5真的來了!官方公開載入速度對比視頻
※史上最幸運的無名小卒:因為溺水而被載入了史冊
※這可能是載入影史的電影
※鎚子雲音樂服務出現「載入失敗」問題,鎚子危機日趨嚴重!
※林徽因的父親也是風雲人物,做的這件事足以載入史冊
※這些不被載入史書的野史秘聞,如此有趣
※SSD對遊戲載入有用嗎?我們測試三款遊戲
※第一次載入武器系統的F-35戰機進行測試,你知道是A還是B么?
※如果LOL所有的載入畫面都是這樣 拳頭恐怕做夢都會笑醒