類從被加載到虛擬機內存開始,到卸載出內存爲止,它的整個生命週期包括:加載、驗證、準備、解析、初始化、使用和卸載7個階段。java
加載、驗證、準備、初始化和卸載這5個階段的順序是肯定的,類的加載過程必須按照這種順序循序漸進的開始,然而,解析階段則不必定:它在某些狀況下能夠在初始化階段以後再開始,這是爲了支持Java語言的運行時綁定(也稱爲動態綁定和晚期綁定)。數組
驗證、準備和解析這3個階段又能夠稱爲鏈接階段。緩存
1、加載:安全
在加載階段,虛擬機須要完成如下3件事情:數據結構
一、經過一個類的全限定名來獲取定義此類的二進制字節流;(這個二進制字節流能夠從任何地方獲取)多線程
二、將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構;函數
三、在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。佈局
加載階段與鏈接階段的部份內容(如一部分字節碼文件格式驗證動做)是交叉進行的,加載階段還沒有完成,鏈接階段可能已經開始,但這些夾在加載階段之中進行的動做,仍然屬於鏈接階段的內容。線程
2、驗證:翻譯
驗證是鏈接階段的第一步,驗證的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全;
驗證階段大體上完成 文件格式驗證、元數據驗證、字節碼驗證和符號引用驗證 4個檢驗動做;
一、文件格式驗證:這一階段驗證字節流是否符合Class文件格式的規範,而且可以被當前版本的虛擬機處理;
驗證點包括:是否以魔數0xCAFeBABE開頭、主次版本號是否在當前虛擬機處理範圍以內 等等;
二、元數據驗證:這一階段是對字節碼描述的信息進行語義校驗,保證不存在不符合Java語言規範的元數據信息;
驗證點包括:這個類是否有父類(除了java.lang.Object以外,全部的類都應該有父類)、這個類的父類是否繼承了不容許被繼承的類(被final類修飾的類)、若是這個類不是抽象類,是否實現了其父類或者接口之中要求實現的全部方法 等等;
三、字節碼驗證:主要目的是經過數據流和控制流分析,肯定程序語義是合法的、符合邏輯的。
驗證點包括:保證任意時刻操做數棧的數據類型與指令代碼序列都能配合工做,例如不會出現這樣的狀況:在操做數棧放置了一個int'類型的數據,使用時卻按照long類型來加載到本地變量表中;保證跳轉指令不會跳轉到方法體之外的字節碼指令上 等等;
四、符號引用驗證:這個驗證發生在虛擬機將符號引用轉化爲直接引用的時候,這個轉化動做將在鏈接的第三階段-----解析階段中發生,符號引用驗證能夠看作是對類自身之外(常量池中的各類符號引用)的信息進行匹配性校驗。
目的是確保解析動做能正常執行。
驗證點包括:符號引用中經過字符串描述的全限定名是否可以找到對應的類;在指定類中是否存在符合方法的字段描述以及簡單名稱所描述的方法和字段;符合引用中的類、字段、方法的訪問性是否能夠被訪問 等等;
3、準備:
準備階段是正式爲類變量(被static修飾的變量)分配內存並設置類型變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配(實例變量將會在對象實例化時隨着對象一塊兒分配在Java堆中)。
類型變量初始值:是一個數據類型的零值,好比 public static int value = 123; 那變量 value 在準備階段事後的初始化值是0而不是123,由於這個時候還沒有開始執行Java 方法,而把 value 賦值爲123 的 putstatic 指令是程序被編譯後存放於類構造器 <cinit>() 方法之中,因此把 value 賦值爲 123 的動做將在初始化階段纔會執行。
可是,若是一個類變量被 final 修飾,如 public static final int value = 123; 那麼準備階段事後 value 的值是123。
4、解析:
解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。
符號引用:符號引用以一組符號來描述所引用的目標,符號可使任何形式的字面量,只要使用時無歧義地定位到目標便可。符號引用與虛擬機實現的內存佈局無關,引用的目標並不必定已經加載到了內存中。
直接引用:直接引用能夠是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是和虛擬機實現的內存佈局相關的,同一個符號引用在不一樣的虛擬機實例上翻譯出來的直接引用通常不會相同。
虛擬機規範沒有規定解析階段發生的具體時間,只是要求在執行 anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、multianewarry、newputfield 和 putstatic 這16個用於操做符號引用的字節碼指令以前,先對他們所使用的符號引用進行解析。
除 invokedynamic 指令以外,虛擬機實現能夠對第一次解析的結果進行緩存,從而避免解析動做重複進行。不管是否真正執行了屢次解析動做,虛擬機須要保證在同一個實體中,若是一個符號引用以前已經被成功解析過,那麼後續的引用解析請求就應當一直成功;一樣的,若是第一次解析失敗,那麼麼其餘指令對這個符號的解析請求也應該收到相同的異常。
解析動做主要針對類或者接口(CONSTANT_Class_info)、字段(CONSTANT_Fieldref_info)、類方法(CONSTANT_Methodref_info)、接口方法(CONSTANT_InterfaceMethodref_info)、方法類型(CONSTANT_MethodType_info)、方法句柄(CONSTANT_MethodHandle_info)和調用點限定符(CONSTANT_InvokeDynamic_info) 7類符號引用進行。
一、類或接口的解析:假如當前代碼所處的類爲D,要把一個從未解析過的符號引用N解析爲一個類或接口C的直接引用,那麼虛擬機完成整個解析過程須要3個步驟:
1)、若是C不是一個數組類型,那虛擬機將會把表明N的全限定名傳遞給D的類加載器區加載這個類C。
2)、若是C是一個數組類型,而且數組元素類型是一個對象,也就是N的描述符會是相似「[Ljava/lang/Integer」的形式,那麼將會按照第一點的規則加載數組元素類型,接着又虛擬機生成一個表明此數組惟獨和元素的數組對象。
3)、若是上述步驟沒有出現任何問題,那麼C在虛擬機中實際上已經成爲了一個有效的類或者接口了,在解析完成以後還須要進行符號引用驗證,確認D是否具有對C的訪問權限。若是發現不具備訪問權限,則拋出 java.lang.IllegalAccessError異常。
二、字段解析:
要解析一個未被解析過的字段符號引用,首先將會對字段表內 class_info 項中索引的 CONSTANT_Class_info 符號引用進行解析,也就是字段所屬的類或接口的符號引用。若是在解析這個類或接口符號引用的過程當中出現了異常,都會致使字段符號引用解析的失敗。若是成功解析,把這個字段所屬類或者接口用C表示,虛擬機按照如下步驟對C進行後續字段的搜索:
1)、若是C自己就包含了簡單名稱和字段描述符都與目標匹配的字段,則返回這個字段的直接引用;
2)、不然,若是在C中實現了接口,將會按照繼承關係從下往上遞歸搜索各個接口和它的父接口,若是接口中包含了簡單名稱和字段描述符都與目標匹配,則返回這個字段的直接引用;
3)、不然,若是C不是 java.lang.Object,將會按照繼承關係從下往上遞歸搜索其父類,若是在父類中包含了簡單名稱和字段描述符都與目標匹配,則返回這個字段的直接引用;
4)、不然,查找失敗,拋出 java.lang.NoSuchFieldError異常;
5)、若是查找返回了引用,則將會對這個字段進行權限校驗,若是發現不具有訪問權限,將會拋出java.lang.IllegalAccessError異常。
三、類方法解析:
先解析出類方法表的class_index 項中索引的方法所屬的類或者接口的符號引用,若是解析成功,用C表示這個類,而後虛擬機按照如下步驟對C進行後續類方法的搜索:
1)、類方法和接口方法符號引用的常量類型定義是分開的,若是在類方法表中發現 class_index 中的索引的C是一個接口,那就直接拋出 java.lang.IncompatibleClassChangeError 異常;
2)、若是經過了第一步,類C中查找是否有簡單名稱和描述符都與目標匹配的方法,若是有則返回這個方法的直接引用;
3)、不然,在類C的父類中遞歸查找是否有簡單名稱和描述符和目標都匹配的方法,若是有返回這個方法的直接引用;
4)、不然,在類C實現的接口及接口的父接口中充下往上遞歸查找,是否有簡單民稱和描述符都和目標匹配的方法,若是有則返回這個方法的直接引用;
5)、不然,查找失敗,拋出 java.lang.NoSuchFieldError異常;
6)、若是查找返回了直接引用,則將會對這個方法進行權限校驗,若是發現不具有訪問權限,將會拋出java.lang.IllegalAccessError異常。
四、接口方法解析:
先解析出接口方法表的 class_index 項中索引的方法所述的類或者接口的符號引用,若是解析成功,用C表示這個接口,而後虛擬機將會按照如下步驟對C進行後續接口方法的搜索;
1)、若是在接口方法表中發現 class_index 中的索引的C是一個類,那就直接拋出 java.lang.IncompatibleClassChangeError 異常;
2)、不然,在接口C中查找是否有簡單名稱和描述符都和目標匹配的方法,若是有,則返回這個方法的直接引用;
3)、不然,在接口C的父接口中遞歸查找是否有簡單名稱和描述符都與目標匹配的方法,若是有,則返回這個方法的直接引用;
4)、不然,查找失敗,拋出 java.lang.NoSuchFieldError異常;
5、初始化:
類初始化階段是類加載過程的最後一步,初始化階段才真正開始執行類中定義的 Java 程序代碼(前面加載階段除了用戶自定義的類加載器參與以外,其他動做都是徹底有虛擬機主導和控制的)。
初始化階段能夠理解爲執行類構造器 <cinit>() 方法的階段。
<cinit>() 方法的特色:
一、<cinit>() 方法是由編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊(static{} 塊)中的語句合併產生的,編譯器收集的順序是由語句在源文件中出現的順序決定,靜態語句塊中只能訪問到定義在靜態語句塊以前的變量,定義在靜態語句塊以後的變臉,在前面的靜態語句快彙總能夠賦值,可是不能訪問;
二、<cinit>() 方法與類的構造函數(實例構造器<init>())不一樣,它不須要顯示地調用父類構造器,虛擬機會保證在子類的<cinit>() 方法執行以前,父類的<cinit>() 方法已經執行完畢(因此虛擬機中第一個被執行<cinit>()方法的類是 java.lang.Object);
三、因爲父類的<cinit>() 方法先執行,也就意味着父類中定義的靜態語句塊要優先於子類的變量賦值操做;
四、<cinit>() 方法對於類和接口來講並非必須的,若是一個類中既沒有靜態語句塊,也沒有對類變量的賦值操做,那麼編譯器就不會產生這個類的 <cinit>() 方法;
五、接口中不能使用靜態語句塊,可是仍然可使用靜態變量初始化的複製操做,所以接口中也會生成 <cinit>() 方法,可是與類不一樣的是,執行接口中的 <cinit>() 方法不須要先執行父接口的 <cinit>() 方法;只有當父接口中定義的變量使用時,父接口才會初始化;另外,接口的實現類在初始化時也同樣不會執行接口的 <cinit>() 方法。
六、虛擬機會保證一個類的 <cinit>() 方法在多線程環境中被正確地加鎖、同步,若是多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的 <cinit>() 方法,其餘線程都須要阻塞等待。