虛擬機的類加載機制定義:把描述類的數據從Class
文件(一串二進制的字節流)加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成被虛擬機直接使用的Java
類型。java
在Java
語言裏,類型的加載、鏈接和初始化過程都是在程序運行期間完成的,Java
裏天生能夠動態擴展的語言特性就是依賴運行期動態加載和動態鏈接這個特色實現的。程序員
用戶能夠經過Java
預約義的和自定義類加載器,讓一個本地的應用程序能夠在運行時從網絡或其餘地方加載一個二進制流做爲程序代碼的一部分。數組
類從被加載到虛擬機內存中開始,到卸載出內存,所通過的生命週期有:安全
其中2-4
統稱爲鏈接,上面的過程有幾個須要注意的點:bash
Java
語言的運行時綁定。有且僅有下面五種狀況必須當即對類進行初始化:網絡
new/getstatic/putstatic/invokestatic
這4
條字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化,場景:new
關鍵字實例化對象final
修飾,已在編譯期把結果放入常量池的字段除外)//1.new關鍵字.
LoadInvokeClass loadInvokeClass = new LoadInvokeClass();
//2.訪問靜態變量
int content = LoadInvokeClass.sContent;
//3.調用靜態方法.
LoadInvokeClass.staticMethod();
複製代碼
java.lang.reflect
包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則須要先觸發其初始化。try {
Class<?> mClass = Class.forName("com.example.lizejun.repojavalearn.load.LoadInvokeClass");
} catch (Exception e) { e.printStackTrace(); }
複製代碼
//其中LoadInvokeClass是LoadInvokeClassChild的父類.
LoadInvokeClassChild classChild = new LoadInvokeClassChild();
複製代碼
main()
方法),虛擬機會先初始化這個主類。JDK 1.7
的動態語言支持時,若是一個java.lang.invoke.MethodHandle
實例最後的解析結果REF_getStatic/REF_putStatic/REF_invokeStatic
的句柄方法,而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化。在2.2
中談到的都是主動引用,除此以外,全部引用類的方法都稱爲被動引用,而被動引用不會觸發類的初始化:數據結構
Object
爲止,可是不會去初始化它所實現的接口,即當咱們初始化ClassChild
的時候,只會先初始化ClassParent
,淡不會初始化ClassInterface
。public interface ClassInterface {}
public class ClassParent implements ClassInterface {
static {
System.out.println("load ClassParent");
}
}
public class ClassChild extends ClassParent {
static {
System.out.println("load ClassChild");
}
}
複製代碼
ClassChild
添加一個靜態屬性,訪問這個靜態屬性不會初始化ClassParent
。public class ClassChild extends ClassParent {
public static int sNumber;
static {
System.out.println("load ClassChild");
}
}
複製代碼
sNumber
,那麼不會引發ClassChild
的實例化。public class ClassChild extends ClassParent {
public static final int sNumber = 2;
static {
System.out.println("load ClassChild");
}
}
複製代碼
ClassChild[] children = new ClassChild[10];
複製代碼
在"加載"階段,虛擬機須要完成如下三件事情:多線程
java.lang.Class
對象,做爲方法區這個類的各類數據的訪問入口。"驗證"階段的目的是爲了確保Class
文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害自身的安全,大體會完成下面四個階段的校驗動做:函數
"準備"階段是正式爲類變量(被static
修飾,而不是實例變量)分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。ui
static
而且非final
的類變量,將被初始化爲數據類型的零值。static
且final
的類變量,在這個階段就會被初始化爲ConstantValue
屬性所指定的值。「解析」階段是虛擬機將常量池的符號引用替換爲直接引用的過程,包括:
根據程序員經過程序指定的主觀計劃去初始化類變量和其它資源,也就是執行類構造器<clinit>()
方法的過程:
<clinit>
方法是由編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊中的語句合併而成,順序是由語句在源文件中出現的順序決定的。靜態語句塊只能訪問到定義在它以前的變量,對於定義在它後面的變量只能賦值不能訪問。
<clinit>()
方法與類的構造函數不一樣,它不須要顯示地調用父類構造器,虛擬機會保證在子類的<clinit>()
方法執行前,父類的<clinit>()
方法已經執行完畢,所以在虛擬機中第一個杯知行的<clinit>()
方法的類確定是java.lang.Object
。
父類的靜態語句塊要優先於子類的變量賦值操做。
若是一個類中沒有靜態語句塊,也沒有對類變量的賦值操做,那麼編譯器能夠不爲這個類生成<clinit>()
方法。
接口不能接口中僅有變量初始化的賦值操做,但執行接口的<clinit>()
方法不須要先執行父接口的<clinit>()
方法,只有當父接口中定義的變量使用時,父接口才會初始化,另外,接口的實現類在初始化時也同樣不會執行接口的<clinit>()
方法。
虛擬機會保證一個類的<clinit>()
方法在多線程環境中被正確地加鎖、同步。
類加載器用來「經過一個類的全限定名來獲取描述此類的二進制字節流」。
類加載器用於實現類的加載動做,除此以外,任意一個類,都須要由它加載它的類加載器和這個類自己一同確立其在Java
虛擬機中的惟一性。
每個類加載器,都擁有一個獨立的類名稱空間,比較兩個類是否相等,只有在兩個類由同一個類加載器加載的前提下才有意義。
相等表明類的Class
對象的equals
方法,isAssignableFrom
方法,isInstance
方法。
絕大部分Java
程序都會用到如下三種系統提供的類加載器:
類加載器之間的層次關係,稱爲類加載器的雙親委派模型,這個模型要求除了頂層的啓動類加載器外,其他的類都應當有本身的父類加載器,通常使用組合來複用父加載器的代碼。
雙親委派模型的工做過程:若是一個類加載器收到了類加載的請求,它首先不會去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,只有當父類加載器反饋本身沒法完成這個加載請求時,子加載器纔會嘗試本身加載。
在類加載過程完畢後,若是須要進行實例化對象就須要通過一下步驟,按優先加載父類,再到子類的順序執行:
咱們用一個簡單的例子: 其中ClassOther
是一個單獨的類:
public class ClassOther {
public int mNumber;
public ClassOther() {
System.out.println("ClassOther Constructor");
}
public void setNumber(int number) {
this.mNumber = number;
}
public int getNumber() {
return mNumber;
}
}
複製代碼
ClassChild
則繼承於ClassChild
:
public class ClassParent {
{
System.out.println("ClassParent before mClassParentContent");
}
private ClassOther mClassParentContent = new ClassOther(10);
{
System.out.println("ClassParent after mClassParentContent=" + mClassParentContent.mNumber);
}
public ClassParent(int number) {
mClassParentContent.setNumber(number);
System.out.println("ClassParent Constructor, mClassParentContent=" + mClassParentContent.mNumber);
}
}
public class ClassChild extends ClassParent {
{
System.out.println("ClassChild before a");
}
private int mClassChildContent = 1;
{
System.out.println("ClassChild after mClassChildContent=" + mClassChildContent);
}
public ClassChild() {
super(2);
System.out.println("ClassChild Constructor");
}
}
複製代碼
當咱們實例化一個ClassChild
對象時,調用的順序以下: