虛擬機將類的數據從Class文件加載到內存,並對數據進行校驗,轉換解析,初始化,最終造成能夠被虛擬機使用的Java類型,稱爲JVM的類加載機制。java
7大過程 :安全
虛擬機須要完成如下 3 件事情:數據結構
1)經過一個類的全限定名來獲取定義此類的二進制字節流。佈局
2)將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。spa
3)在內存中生成一個表明這個類的 java.lang.Class 對象,做爲方法區這個類的各類數據的訪問入口。3d
是鏈接階段的第一步,這一階段的目的是爲了確保 Class 文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。對象
但從總體上看,驗證階段大體上會完成下面 4 個階段的檢驗動做:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證blog
是正式爲類變量(static)分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。繼承
這個階段中有兩個容易產生混淆的概念須要強調一下:
1.首先,這時候進行內存分配的僅包括類變量(被 static 修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨着對象一塊兒分配在 Java堆中。接口
2.其次,這裏所說的初始值「一般狀況」下是數據類型的零值,假設一個類變量的定義爲:public static int value=123;
那變量 value 在準備階段事後的初始值爲 0 而不是 123,由於這時候還沒有開始執行任何 Java 方法,而把 value 賦值爲 123 的 putstatic 指令是程序被編譯後,存放於類構造器<clinit>()方法之中,因此把 value 賦值爲 123 的動做將在初始化階段纔會執行。
假設上面類變量 value 的定義變爲:public static final int
value=123;編譯時 Javac 將會爲 value 生成 ConstantValue 屬性,在準備階段虛擬機就會根據 ConstantValue 的設置將 value 賦值爲 123。
解析是虛擬機將常量池內的符號引用替換爲直接引用的過程。
解析動做主要針對類或接口丶字段丶類方法丶接口方法丶方法類型丶方法句柄和調用點限定符7類符號引用,分別對應於常量池的CONSTANT_Class_info,CONSTANT_Fieldref_info,CONSTANT_Methodref_info,
CONSTANT_InterfaceMethodref_info,CONSTANT_MethodType_info,
CONSTANT_MethodHandle_info, CONSTANT_InvokeDynamic_info7種常量類型。
4.1類或接口的解析
4.2字段解析
4.3類方法解析
4.4接口方法解析
至於剩下三種則與JDK7新增的動態語言支持息息相關。
方法調用詳解
方法調用並不等同於方法執行,方法調用階段惟一的任務就是肯定被調用方法的版本(即調用哪個方法),暫時不涉及方法內部的具體運行過程。Class文件的編譯過程當中不包含傳統編譯的鏈接步驟,一切方法調用在class文件中存儲的都只是符號引用,而不是方法在實際運行內存佈局中的入口地址即直接引用。
所以,這個特性給java帶來強大擴展性,有些方法調用須要在類加載期間,甚至到運行期間才肯定方法的直接引用。
a:解析
調用目標在程序代碼寫好、編譯器進行編譯時就必須肯定下來。這類方法的調用稱爲解析。
在Java中符合編譯期可知,運行期不改變要求的方法:主要包括靜態方法和私有方法兩大類,前者與類型(class對象)直接關聯,後者在外部不可被訪問,這兩種方法各自的特色決定了它們都不可能經過繼承或別的方式重寫其餘版本,所以它們都適合在類加載階段進行解析。
b.分派
Java是一門面向對象的語言,具有面向對象的三個基本特徵:繼承丶封裝丶多態。分派調用過程將揭示多態的一些最基本提現,如重載和重寫在JVM中是如何肯定正確的目標方法的。
靜態分派
結果是輸出2個hello,guy。
「Human」稱爲變量的靜態類型(Static Type),或者叫作的外觀類型(Apparent Type),後面的「Man」則稱爲變量的實際類型(Actual Type),靜態類型和實際類型在程序中均可以發生一些變化,區別是靜態類型的變化僅僅在使用時發生,變量自己的靜態類型不會被改變,而且最終的靜態類型是在編譯期可知的;而實際類型變化的結果在運行期纔可肯定,編譯器在編譯程序的時候並不知道一個對象的實際類型是什麼。代碼中定義了兩個靜態類型相同但實際類型不一樣的變量,但虛擬機(準確地說是編譯器)在重載時是經過參數的靜態類型而不是實際類型做爲斷定依據
的。而且靜態類型是編譯期可知的,所以,在編譯階段,Javac 編譯器會根據參數的靜態類型決定使用哪一個重載版本,因此選擇了 sayHello(Human)做爲調用目標。
總結:全部依賴靜態類型來定位方法執行版本的分派動做稱爲靜態分派。
靜態分派的典型應用是方法重載。靜態分派發生在編譯階段,所以肯定
靜態分派的動做實際上不是由虛擬機來執行的。
動態分派
看如上代碼,靜態類型一樣都是 Human 的兩個變量 man 和 woman 在調用 sayHello()方法時執行了不一樣的行爲,而且變量 man 在兩次調用中執行了不一樣的方法。致使這個現象的緣由很明顯,是這兩個變量的實際類型不一樣。
在實現上,最經常使用的手段就是爲類在方法區中創建一個虛方法表。虛方法表中存放着各個方法的實際入口地址。若是某個方法在子類中沒有被重寫,那子類的虛方法表裏面的地址入口和父類相同方法的地址入口是一致的,都指向父類的實現入口。若是子類中重寫了這個方法,子類方法表中的地址將會替換爲指向子類實現版本的入口地址。圖中,Son 重寫了來自 Father 的所有方法,所以 Son 的方法表沒有指向 Father 類型數據的箭頭。可是 Son 和 Father都沒有重寫來自 Object 的方法,因此它們的方法表中全部從 Object 繼承來的方法都指向了 Object 的數據類型。
初始化階段,虛擬機規範則是嚴格規定了有且只有 5 種狀況必須當即對類進行「初始化」(而加載、驗證、準備天然須要在此以前開始):
1)遇到 new、getstatic、putstatic 或 invokestatic 這 4 條字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化。生成這 4 條指令的最多見的Java 代碼場景是:使用 new 關鍵字實例化對象的時候、讀取或設置一個類的靜態字段(被 final 修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候,以及調用一個類的靜態方法的時候。
2)使用 java.lang.reflect 包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則須要先觸發其初始化。
3)當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化。
4)當虛擬機啓動時,用戶須要指定一個要執行的主類(包含 main()方法的那個類),虛擬機會先初始化這個主類。
5)當使用 JDK 1.7 的動態語言支持時,若是一個 java.lang.invoke.MethodHandle 實例最後的解析結果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化。
最後就是使用以及卸載階段了。
後續補充