虛擬機把描述類的數據從class文件加載到內存,並對數據進行校驗,轉換分析和初始化,最終形成可以被虛擬節直接使用的JAVA類型,這就是虛擬機的類加載機制。
類從被加載到虛擬機內存到卸載出內存的生命周期包括:加載->連接(驗證->准備->解析)->初始化->使用->卸載
初始化的5種情況:
1.使用new關鍵字實例化對象時,讀取或設置一個類的靜態字段,除被final修飾經編譯結果放在常量池的靜態字段,調用類的靜態方法時。 2.使用java.lang.reflect包方法對類進行反射調用時。(Class.forName())。 3.初始化子類時,如果父類沒有初始化。 4.虛擬機啟動時main方法所在的類。 5.當使用JDK1.7動態語言支持時,java.lang.invoke.MethodHandle實例解析結果為REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,且對應類沒有進行初始化。
加載 加載是類加載的第一個階段,虛擬機要完成以下三個過程:
1.通過類的全限定名獲取定義此類的二進制字節流。 2.將字節流的存儲結構轉化為方法區的運行時結構。 3.在內存中生成一個代表該類的Class對象,作為方法區各種數據的訪問入口。
驗證 目的是確保class文件字節流信息符合虛擬機的要求。
准備 為static修飾的變量賦初值,例如int型默認為0,boolean默認為false。
解析 虛擬機將常量池內的符號引用替換成直接引用。
初始化 初始化是類加載的最后一個階段,將執行類構造器< init>()方法,注意這里的方法不是構造方法。該方法將會顯式調用父類構造器,接下來按照java語句順序為類變量和靜態語句塊賦值。
方法調用
Java是一門面向對象的語言,它具有多態性。那么虛擬機又是如何知道運行時該調用哪一個方法?
靜態分派是在編譯期就決定了該調用哪一個方法而不是由虛擬機來確定,方法重載就是典型的靜態分派。 動態分派是在虛擬機運行階段才能決定調用哪一個方法,方法重寫就是典型的動態分派。
動態分派的實現:當調用一個對象的方法時,會將該對象的引用壓棧到操作數棧,然后字節碼指令invokevirtual會去尋找該引用實際類型。如果在實際類型中找對應的方法,且訪問權限足夠,則直接返回該方法引用,否則會依照繼承關系對父類進行查找。實際上,如果子類沒有重寫父類方法,則子類方法的引用會直接指向父類方法。
由Java虛擬機自帶的類加載器所加載的類,在虛擬機的生命周期中,始終不會被卸載。
前面介紹過,Java虛擬機自帶的類加載器包括根類加載器、擴展類加載器和系統類加載器。
Java虛擬機本身會始終引用這些類加載器,而這些類加載器則會始終引用它們所加載的類的Class對象,因此這些Class對象始終是可觸及的。
由用戶自定義的類加載器加載的類是可以被卸載的。
具體的例子為:

loader1變量和obj變量間接應用代表Sample類的Class對象,而objClass變量則直接引用它。
如果程序運行過程中,將上圖左側三個引用變量都置為null,此時Sample對象結束生命周期,MyClassLoader對象結束生命周期,代表Sample類的Class對象也結束生命周期,Sample類在方法區內的二進制數據被卸載。
當再次有需要時,會檢查Sample類的Class對象是否存在,如果存在會直接使用,不再重新加載;如果不存在Sample類會被重新加載,在Java虛擬機的堆區會生成一個新的代表Sample類的Class實例(可以通過哈希碼查看是否是同一個實例)。
