JVM相關概念java
- jdk
jdk(Java Development Kit)Java開發包,是Java開發人員用於編譯和調試程序的一套程序的集合。
- jre
jre(Java Runtime Evironment)Java運行時環境,是運行Java程序的平臺,全部的Java程序必須在這個平臺中才能執行。
- jvm
jvm(Java Virtual Machine)Java虛擬機,是用代碼虛擬出來的計算機,模擬執行計算機的各項功能,它有本身的硬件架構,如:處理器、堆棧、寄存器等,還有本身的一套指令系統,在不一樣的操做系統上均可以安裝JVM,從而實現Java程序在不一樣的操做系統上都能執行,JVM就是爲實現Java的跨平臺特性。
JVM加載類的過程面試
咱們執行Java程序開發出來後,須要先編譯再執行,JVM就負責加載類的過程。
安全
類加載的過程分爲:網絡
- 加載
- 驗證
- 準備
- 解析
- 初始化
類加載的具體過程架構
下面詳細介紹下這幾個過程:jvm
-
加載
在加載類的過程要完成:佈局
- 根據類的全名限定符,獲取class二進制流,這個流能夠從磁盤上的class、jar文件得到,也能夠從網絡中得到。
- 將類的靜態存儲結構轉化爲方法區的運行時動態存儲結構
- 在內存的堆中生成對應的java.lang.Class對象,做爲方法區的入口
-
驗證
加載類完成後,就進入了驗證過程,這個過程保證了前面生成的Class對象中的信息,不會危害JVM的安全。
須要驗證的方面有:操作系統
- 文件格式驗證,是要驗證字節流是否符合Class文件格式的規範,而且能被當前版本的虛擬機處理。如驗證魔數是否0xCAFEBABE;主、次版本號是否正在當前虛擬機處理範圍以內;常量池的常量中是否有不被支持的常量類型等等,該驗證階段的主要目的是保證輸入的字節流能正確地解析並存儲於方法區中,通過這個階段的驗證後,字節流纔會進入內存的方法區中存儲,因此後面的三個驗證階段都是基於方法區的存儲結構進行的。
- 元數據驗證,是對字節碼描述的信息進行語義分析,以保證其描述的信息符合Java語言規範的要求。可能包括的驗證如:這個類是否有父類;這個類的父類是否繼承了不容許被繼承的類;若是這個類不是抽象類,是否實現了其父類或接口中要求實現的全部方法。
- 字節碼驗證,主要工做是進行數據流和控制流分析,保證被校驗類的方法在運行時不會作出危害虛擬機安全的行爲。若是一個類方法體的字節碼沒有經過字節碼驗證,那確定是有問題的;但若是一個方法體經過了字節碼驗證,也不能說明其必定就是安全的。
- 符號引用驗證,發生在虛擬機將符號引用轉化爲直接引用的時候,這個轉化動做將在「解析階段」中發生。驗證符號引用中經過字符串描述的權限定名是否能找到對應的類;在指定類中是否存在符合方法字段的描述符及簡單名稱所描述的方法和字段;符號引用中的類、字段和方法的訪問性(private、protected、public、default)是否可被當前類訪問。
-
準備
準備階段會在方法區中爲類的靜態變量分配內存,並賦給默認值。指針
public static int count = 100;
如:上面的count變量在準備階段會賦值爲0,在初始化時再賦值爲100;調試
-
解析
解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。
- 符號引用(Symbolic Reference)
符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時能無歧義地定位到目標便可。符號引用與虛擬機實現的內存佈局無關,引用的目標並不必定已經加載到內存中。
- 直接引用(Direct Reference)
直接引用能夠是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是與虛擬機實現的內存佈局相關的,若是有了直接引用,那麼引用的目標一定已經在內存中存在。
-
初始化
類初始化是類加載過程的最後一步,前面的類加載過程,除了在加載階段用戶應用程序能夠經過自定義類加載器參與以外,其他動做徹底由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的Java程序代碼。
初始化階段是執行類構造器<clinit>()方法的過程。<clinit>()方法是由編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊(static{}塊)中的語句合併產生的。
那麼什麼時候執行初始化呢?
- 建立類的實例
- 訪問類的靜態變量(除常量外,final修飾的)
緣由:常量一種特殊的變量,由於編譯器把他們看成值而不是屬性來對待。
- 訪問類的靜態方法
- 反射如(Class.forName("com.test.Person"))
- 當初始化一個類時,發現其父類還未初始化,則先調用父類的初始化
- 虛擬機啓動時,定義了main()方法的那個類先初始化
代碼案例
瞭解了類的加載機制,咱們來看一道面試題:
public class MySingleton {
private static MySingleton singleton = new MySingleton();
public static int count1 = 0;
public static int count2;
private MySingleton(){
count1++;
count2++;
}
public static MySingleton getInstance(){
return singleton;
}
public static void main(String[] args) {
MySingleton singleton = MySingleton.getInstance();
System.out.println("count1-->"+MySingleton.count1);
System.out.println("count2-->"+MySingleton.count2);
}
}
上面的結果,大多數同窗可能認爲兩個靜態變量都是1,結果比較意外:
count1-->0
count2-->1
這是爲何呢?下面咱們來分析下:
- 首先咱們知道在類的準備階段會爲靜態變量賦默認值:
singleton = null;
count1 = 0;
count2 = 0;
- 當調用類的靜態方法getInstance後,引起類的初始化,先執行new MySingleton() 調用構造方法,這時:
count1 = 1;
count2 = 1;
- 繼續初始化,爲變量賦值,count1賦值爲0,count2沒有賦值就保留值1,結果就是:
count1 = 0;
count2 = 1;
總結
JVM是代碼模擬的計算機,有本身的硬件和軟件,JVM能實現Java類的加載和運行,具體加載過程有:加載、驗證、準備、解析、初始化5個步驟組成。