Class文件由類裝載器裝載後,在JVM中將造成一份描述Class結構的元信息對象,經過該元信息對象能夠獲知Class的結構信息:如構造函數,屬性和方法等,Java容許用戶藉由這個Class相關的元信息對象間接調用Class對象的功能。java
虛擬機把描述類的數據從class文件加載到內存,並對數據進行校驗,轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。數據庫
類裝載器就是尋找類的字節碼文件,並構造出類在JVM內部表示的對象組件。在Java中,類裝載器把一個類裝入JVM中,要通過如下步驟:數組
(1) 裝載:查找和導入Class文件;安全
(2) 連接:把類的二進制數據合併到JRE中;網絡
(a)校驗:檢查載入Class文件數據的正確性;數據結構
(b)準備:給類的靜態變量分配存儲空間;多線程
(c)解析:將符號引用轉成直接引用;ide
(3) 初始化:對類的靜態變量,靜態代碼塊執行初始化操做
函數
(2) 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構oop
(3) 在Java堆中生成一個表明這個類的java.lang.Class對象,做爲方法區這些數據的訪問入口。(不一樣虛擬機機制不一樣,hotsport把Class對象放在方法區中)
虛擬機規範中並無準確說明二進制字節流應該從哪裏獲取以及怎樣獲取,這裏能夠經過定義本身的類加載器去控制字節流的獲取方式,譬如:網絡、動態生成、數據庫等。
public static int value=123;那變量value在準備階段事後的初始值爲0而不是123.由於這時候還沒有開始執行任何java方法,而把value賦值爲123的putstatic指令是程序被編譯後,存放於類構造器()方法之中,因此把value賦值爲123的動做將在初始化階段纔會執行。
解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。解析動做主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號引用進行。
CONSTANT_Class_info
CONSTANT_Field_info
CONSTANT_Method_info
符號引用與虛擬機實現的佈局無關,引用的目標並不必定要已經加載到內存中。各類虛擬機實現的內存佈局能夠各不相同,可是它們能接受的符號引用必須是一致的,由於符號引用的字面量形式明肯定義在Java虛擬機規範的Class文件格式中。
直接引用能夠是指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。若是有了直接引用,那引用的目標一定已經在內存中存在。
public class Test { static { i=0; System.out.println(i);//這句編譯器會報錯:Cannot reference a field before it is defined(非法向前應用) } static int i=1; }<clinit>()方法與實例構造器<init>()方法不一樣,它不須要顯示地調用父類構造器,虛擬機會保證在子類<init>()方法執行以前,父類的<clinit>()方法方法已經執行完畢, 因爲父類的<clinit>()方法先執行,也就意味着父類中定義的靜態語句塊要優先於子類的變量賦值操做。以下:
public class Parent { public static int A = 1; static { A = 2; } static class Sub extends Parent { public static int B = A; } public static void main(String[] args) { System.out.println(Sub.B); //2 } }<clinit>()方法對於類或者接口來講並非必需的,若是一個類中沒有靜態語句塊,也沒有對變量的賦值操做,那麼編譯器能夠不爲這個類生產<clinit>()方法。
public class DealLoopTest { static class DeadLoopClass { static { if(true) { System.out.println(Thread.currentThread()+"init DeadLoopClass"); while(true) { } } } } public static void main(String[] args) { Runnable script = new Runnable(){ public void run() { System.out.println(Thread.currentThread()+" start"); DeadLoopClass dlc = new DeadLoopClass(); System.out.println(Thread.currentThread()+" run over"); } }; Thread thread1 = new Thread(script); Thread thread2 = new Thread(script); thread1.start(); thread2.start(); } }
Thread[Thread-0,5,main] start Thread[Thread-1,5,main] start Thread[Thread-0,5,main]init DeadLoopClass須要注意的是,其餘線程雖然會被阻塞,但若是執行<clinit>()方法的那條線程退出<clinit>()方法後,其餘線程喚醒以後不會再次進入<clinit>()方法。同一個類加載器下,一個類型只會初始化一次。
static { System.out.println(Thread.currentThread() + "init DeadLoopClass"); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } }
Thread[Thread-0,5,main] start Thread[Thread-1,5,main] start Thread[Thread-1,5,main]init DeadLoopClass (以後sleep 10s) Thread[Thread-1,5,main] run over Thread[Thread-0,5,main] run over虛擬機規範嚴格規定了有且只有5中狀況(jdk1.7)必須對類進行「初始化」(而加載、驗證、準備天然須要在此以前開始):
(1) 遇到new、getstatic、putstatic或invokestatic這4條字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化。生成這4條指令的最多見的Java代碼場景是:使用new關鍵字實例化對象的時候,讀取或設置一個類的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候,以及調用一個類的靜態方法的時候。
(2) 使用java.lang.reflect包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則須要先觸發其初始化。
(3) 當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化。
(4) 當虛擬機啓動時,用戶須要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。
(5) 當使用jdk1.7動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,而且這個方法句柄所對應的類沒有進行初始化,則須要先出觸發其初始化。
只有上述這五種狀況會觸發初始化,也稱爲對一個類進行主動引用,除此之外,全部其餘方式都不會觸發初始化,稱爲被動引用.
注意如下幾種狀況不會執行類初始化: