一、什麼是 JVM JVM(Java Virtual Machine)是一個可以執行 Java 位元組碼文件(即 .class 文件)的虛擬機進程。當 Java 源文件能被成功編譯成 .class 文件,就能在不同平臺上的不同版本的 JVM 運行,因為 JVM 能將相同的 .class 文件解釋稱不同 ...
一、什麼是 JVM
JVM(Java Virtual Machine)是一個可以執行 Java 位元組碼文件(即 .class 文件)的虛擬機進程。當 Java 源文件能被成功編譯成 .class 文件,就能在不同平臺上的不同版本的 JVM 運行,因為 JVM 能將相同的 .class 文件解釋稱不同平臺的機器碼。正是因為 JVM 的存在,Java 被稱為與平臺無關的語言。
一般而言,.java 文件經過編譯後會得到 .class 文件,而將這個文件載入到記憶體之前需要先通過類載入器,先簡單過一下圖:
二、類載入過程
類載入的過程為: 載入-->連接(驗證-->準備-->解析)-->初始化。下麵介紹其中的幾個過程。
1、載入
這個過程主要是通過類的全限定名,例如 java.lang.String 這樣帶上包路徑的類名,獲取到位元組碼文件;然後將這個位元組碼文件代表的靜態存儲結構(可簡單理解為對象創建的模板)存在方法區,併在堆中生成一個代表此類的 Class 類型的對象,作為訪問方法區中“模板”的入口,往後創建對象的時候就按照這個模板創建。
舉個例子,有時候通過反射創建對象,像當初學 JDBC 時會通過 Class.getName("com.mysql.jdbc.Driver.class").newInstance() 創建對象,通過 Class 和相應的全限定類名獲取到方法區中的“模板”然後創建對象。
2、驗證
驗證過程主要確保被載入的類的正確性。首先要先驗證文件格式是否規範,如果只是通過 .class 尾碼來辨別,那隨便把尾碼名改一下就可以跑程式了,那豈不是很容易出事。來看看位元組碼文件大概是長什麼樣的:
註意看首碼 cafe babe(咖啡寶貝?)這隻是驗證的其中一個點,還會驗證位元組碼文件里是否包含主次版本號等驗證信息。
3、準備
這個階段主要是給類變數(靜態變數)分配方法區的記憶體並初始化。實例變數不是在這個階段分配記憶體,實例變數是隨著對象一起分配在堆中。另外,給靜態變數初始化為零值或空值,比如public static int n=5;這裡並不是馬上給 n 這個變數賦值為 5,而是先將其賦值為 0,類似的,如果是引用數據類型,則預設為 null。還有一點需要註意的是,對於 final 類型的數據,必須在程式內給它賦值,系統不會自動初始化,例如 static String str = "hello" + “world”;String 是 final 類型的,在編譯階段就給它優化成 static String str = "helloworld” ,並且將 "helloworld" 放進了常量池。
4、初始化
這個階段就是將靜態變數賦值為初始值,還是 public static int n=5; 這回給 n 賦值為 5 了。
三、類載入器
啟動類載入器是由C/C++寫的,主要負責載入 jre\lib 目錄下的類;擴展類載入器主要負責載入 jre\lib\ext 目錄下的類;而應用程式類載入器主要負責載入我們自己編寫的類;當然還能自己寫類載入器,即自定義載入器。程式主要由前面三個類載入器相互配合載入的。
public class Main { public static void main(String[] args) { Main main = new Main(); System.out.println(main.getClass().getClassLoader()); System.out.println(main.getClass().getClassLoader().getParent()); System.out.println(main.getClass().getClassLoader().getParent().getParent()); } }
由於啟動類載入器是 C/C++ 語言寫的,所以輸出為 null
雙親委派機制
在類載入的過程中,存在著雙親委派機制,即當要載入一個類時,先由父類載入器載入,當父類載入器沒辦法載入時,才由下麵的載入器載入,來看一個程式:
package java.lang; // 自定義的包 public class String { public static void main(String[] args) { System.out.println("這是自定義的java.lang.String類"); } }
由於 jre\lib\ext 中存在 java.lang.String 類,當載入該類的時候,根據全限定名進行查找,找到後由啟動類載入器載入,發現 String 類中不包含 main() 方法,因此程式出錯。