JVM(Java Virtual Machine)是一個能夠執行 Java 字節碼文件(即 .class 文件)的虛擬機進程。當 Java 源文件能被成功編譯成 .class 文件,就能在不一樣平臺上的不一樣版本的 JVM 運行,由於 JVM 能將相同的 .class 文件解釋稱不一樣平臺的機器碼。正是由於 JVM 的存在,Java 被稱爲與平臺無關的語言。java
通常而言,.java 文件通過編譯後會獲得 .class 文件,而將這個文件加載到內存以前須要先經過類加載器,先簡單過一下圖:mysql
類加載的過程爲: 加載-->鏈接(驗證-->準備-->解析)-->初始化。下面介紹其中的幾個過程。sql
這個過程主要是經過類的全限定名,例如 java.lang.String 這樣帶上包路徑的類名,獲取到字節碼文件;而後將這個字節碼文件表明的靜態存儲結構(可簡單理解爲對象建立的模板)存在方法區,並在堆中生成一個表明此類的 Class 類型的對象,做爲訪問方法區中「模板」的入口,日後建立對象的時候就按照這個模板建立。優化
舉個例子,有時候經過反射建立對象,像當初學 JDBC 時會經過 Class.getName("com.mysql.jdbc.Driver.class").newInstance() 建立對象,經過 Class 和相應的全限定類名獲取到方法區中的「模板」而後建立對象。spa
驗證過程主要確保被加載的類的正確性。首先要先驗證文件格式是否規範,若是隻是經過 .class 後綴來辨別,那隨便把後綴名改一下就能夠跑程序了,那豈不是很容易出事。來看看字節碼文件大概是長什麼樣的:code
注意看前綴 cafe babe(咖啡寶貝?)這只是驗證的其中一個點,還會驗證字節碼文件裏是否包含主次版本號等驗證信息。對象
這個階段主要是給類變量(靜態變量)分配方法區的內存並初始化。實例變量不是在這個階段分配內存,實例變量是隨着對象一塊兒分配在堆中。另外,給靜態變量初始化爲零值或空值,好比public static int n=5;這裏並非立刻給 n 這個變量賦值爲 5,而是先將其賦值爲 0,相似的,若是是引用數據類型,則默認爲 null。還有一點須要注意的是,對於 final 類型的數據,必須在程序內給它賦值,系統不會自動初始化,例如 static String str = "hello" + 「world」;String 是 final 類型的,在編譯階段就給它優化成 static String str = "helloworld」 ,而且將 "helloworld" 放進了常量池。blog
這個階段就是將靜態變量賦值爲初始值,仍是 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() 方法,所以程序出錯。