1. 程式人生 > >JVM類載入機制詳解,建議看這一篇就夠了,深入淺出總結的十分詳細!

JVM類載入機制詳解,建議看這一篇就夠了,深入淺出總結的十分詳細!

## 類載入機制 虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,這就是虛擬機器的類載入機制。 ## 類載入的時機 * 遇到new(比如new Student())、getstatic和putstatic(讀取或設定一個類的靜態欄位,如下程式碼,讀取被final修飾並已在編譯器把結果放入常量池的靜態欄位除外)、invokestatic(呼叫類的靜態方法)這四條指令時,如果對應的類沒有初始化,則要對對應的類先進行初始化。 ``` public class Student{ private static int age; public static void method(){ } } Student.age Student.method(); ``` * 使用java.lang.reflect包方法時對類進行**反射呼叫**的時候。 * 初始化一個類的時候發現其父類還沒初始化,要先初始化其父類。 * 當虛擬機器開始啟動時,使用者需要指定一個主類(main),虛擬機器會限制性這個主類的初始化。 ## 類載入的過程 類載入過程是如下圖所示的一個流水線過程,其中連線過程可細化為驗證、準備和解析三個小步驟。 ![](https://upload-images.jianshu.io/upload_images/23140115-57dd8c82aba837d1?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ## 載入 > class檔案–>class物件 “載入”過程主要是靠類載入器實現的,包括使用者自定義類載入器。 ![](https://upload-images.jianshu.io/upload_images/23140115-4af7570fc69446c2?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ## 載入的過程 在載入過程中,JVM主要做以下3件事: 1. **通過一個類的全限定名來獲取定義此類的二進位制位元組流(class檔案)**。在程式執行過程中,當要訪問一個類時,若發現這個類尚未被載入,並滿足類初始化的條件時,就根據要被初始化的這個類的全限定名找到該類的二進位制位元組流,開始載入過程 2. **將這個位元組流的靜態儲存結構轉化為方法區的執行時資料結構(即Class物件)** 3. **在記憶體中建立一個該類的java.lang.Class物件,作為方法區該類的各種資料的訪問入口** 程式在執行中所有對該類的訪問都通過這個類物件,也就是這個Class物件是提供給外界訪問該類的介面。 ## 載入源 JVM規範對於載入過程給予了較大的寬鬆度,一般二進位制位元組流都從已經編譯好的**本地class檔案**中讀取,此外還可以從這些地方讀取:zip包(jar、war、ear等),由jsp檔案中生成對應的Class類,資料庫,網路,執行時計算生成(動態代理技術)。 ## 載入過程的注意點 * 類和陣列載入的區別:非陣列類是**由類載入器來完成**;陣列類本身不通過類載入器建立,它是**由java虛擬機器直接建立**,但陣列類與類載入器有很密切的關係,因為陣列類的元素型別最終要靠類載入器建立。 * HotSpot將Class物件存放在方法區 ## 驗證 > 各種檢查 驗證階段比較耗時,它非常重要但不一定必要,可用-Xverify:none引數關閉,以縮短類載入時間。 ## 驗證的目的 保證二進位制位元組流的資訊符合虛擬機器規範,並沒有安全問題。 ## 驗證的必要性 Java語言的安全性是通過編譯器來保證的,但編譯器和虛擬機器是兩個獨立的東西,虛擬機器只認二進位制位元組流,它不會管所獲得的二進位制位元組流是哪來的。當然,如果是編譯器給它的那麼就相對安全,但如果是從其它途徑獲得的,那麼無法確保該二進位制位元組流是安全的。 ## 驗證的過程 ![](https://upload-images.jianshu.io/upload_images/23140115-d840bae1763314a0?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 其中檔案格式驗證階段是**基於二進位制位元組流**進行的,**只有通過本階段驗證,才被允許存放到方法區**。後面的三個驗證階段都是基於方法區的儲存結構進行,不會再直接操作位元組流。 ## 準備 > 為static分配記憶體並初始化0值。JDK1.7之前在方法區,1.7之後在堆。 僅僅為類變數(即static修飾的欄位變數)分配記憶體並且設定該類變數的初始值即零值,這裡不包含用final修飾的static,因為final在編譯的時候就會分配好,同時這裡也不會為例項變數分配初始化。類變數(靜態變數)會分配在方法區中,而例項變數是會隨著物件一起分配到Java堆中。 準備階段主要完成兩件事情: * 為已在方法區中的類的靜態成員變數分配記憶體; * 為靜態成員變數設定初始值,具體初始值為下圖所示。 ![在這裡插入圖片描述](https://upload-images.jianshu.io/upload_images/23140115-e0b729bcfac25a0c?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 注意: ``` public static int x = 1000; ``` 實際上變數x在準備階段過後的初始值為0,而不是1000。將x賦值為1000是在初始化階段完成。 ## 解析 > 將符號引用替換為直接引用 解析是虛擬機器將常量池的符號引用替換為直接引用的過程。 ## 初始化 >